본문 바로가기
  • 살짝 구운 김 유나
Project/zum:go

[React] axios로 다중 파일 전송 - 이미지, JSON

by yunae 2023. 2. 7.

나는,, 상품의 정보를 등록할 때, JSON(제목, 설명, 현재 상태,, 등), 과 상품의 이미지를 함께 post로 보내고 싶었다!

-> 파일과 JSON을 같이 보내줘야 한다는 사실이 나는 좀,,, 어려웠다,,

 

 

FormData ?

AJAX로 form 전송을 가능하게 하는 객체!
=> 업로드한 파일을 서버에게 전송할 수 있도록 하는 객체

이미지 파일을 서버에 업로드하기 위해서는 FormData 객체를 사용해야 한다.

-> key 값과 value의 쌍의 형태! (딕셔너리 넊김)

// 사용하기
let formData = new FormData();
formData.append("key","value");  // 키-값 쌍으로 formData에 추가한다. 여러개도 추가할 수 있음
formData.append("key","value11"); // 이런 식으로,, 

// 값 확인하기
formData.has("key")  // true, 해당하는 키의 존재 여부 확인
formData.get("key")  // value, 해당하는 키와 매핑되어 있는 값 확인
formData.getAll("key") // ["value", "value11"]

// 삭제하기
formData.delete("key") 
formData.get("key") // null

 

 

파일 입력 받기 - input [ type = file ]

: 하나 이상의 파일을 선택해 입력할 수 있다

<input
  className={styles.file}
  type="file"          // 파일로 입력 받음
  accept="image/*"     // 이미지 유형의 파일만 받기
  capture="camera"     // 모바일에서 직접 카메라가 호출될 수 있도록 하는,,,근데 이제,, 나는 안해본,,
  name="imgurls"       // 담긴 파일을 참조할 때 사용할 이름
  multiple            // 다중 업로드
/>

 

 

 

formData 만들기

FormData에 값을 담기 위해서 FormData에서 제공하는 append() 함수를 사용한다.

JSON 데이터는  content-type을 'application/json' 으로 명시한다.

  const userId = useSelector((state) => {
    return state.user.userCode;
  });
  
  const [title, setTitle] = useState("");
  const [price, setPrice] = useState("");
  const [description, setDescription] = useState("");
  const [availableTime, setAvailableTime] = useState("");

  const content = {
    title,
    price,
    description,
    availableTime,
    status: "ONSALE",
    userId,
  };

JSON.stringify() 메소드를 통해 JSON 객체를 텍스트로 변환한 후 new Blob()을 통해서 값을 append 한다.

// input태그의 이름을 여기서 사용해요,,@
let files = e.target.imgurls.files;

// file List를 순회하면서 formData에 append
for (let i = 0; i < files.length; i++) {
  formData.append("imgUrl", files[i]);
}

// JSON 데이터는 new Blob 으로!
formData.append(
  "content",
  new Blob([JSON.stringify(content)], { type: "application/json" })
);

 

여기서,

console.log(formData)

를 실행해도 빈 객체(?) 가 나온다... 당황하지 말고

// formData에 저장된 값 확인 하기  
for (var key of formData.keys()) {
  console.log(key, formData.get(key), "👩");
}

이렇게 확인하면 된단다..! 나는 한참동안 왜 데이터가 안들어가고 있을까,,,긁적긁적 30분 함,,,

 

 

 

 

axios 보내기

- 파일을 전송하려면 Content-type : multipart/form-data로 정해준다.

await axios
  .post("http://localhost:8080/product", formData, {
  	// Headers에 어떤 종류의 데이터를 전송하는지 알려준다
    headers: {
      "Content-Type": "multipart/form-data",
    },
  })
  .then((res) => {
    // navigate(`/detail/${res.data}`)
    console.log(res.data)
  })
  .catch((err) => {
    console.log(err);
  });

 

 

 

전체 코드

import React, { useState, useEffect } from "react";
import styles from "./styles/AddProduct.module.css";
import { ChevronLeftIcon, CameraIcon } from "@heroicons/react/24/solid";
import axios from "axios";
import testImg from "../assets/images/kim.png";
import { useLocation, useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";

export default function AddProduct() {
  const navigate = useNavigate();
  // const location = useLocation();
  // const { userId } = location.state;
  // console.log(location.state)
  
  // redux
  const userId = useSelector((state) => {
    return state.user.userCode;
  });
  
  const [title, setTitle] = useState("");
  const [price, setPrice] = useState("");
  const [description, setDescription] = useState("");
  const [availableTime, setAvailableTime] = useState("");

  const content = {
    title,
    price,
    description,
    availableTime,
    status: "ONSALE",
    userId,
  };

  // 상품등록 axios
  const handleSubmit = async (e) => {
    e.preventDefault();

    let formData = new FormData();
    let files = e.target.imgurls.files;

    for (let i = 0; i < files.length; i++) {
      formData.append("imgUrl", files[i]);
    }

    formData.append(
      "content",
      new Blob([JSON.stringify(content)], { type: "application/json" })
    );

    await axios
      .post("http://localhost:8080/product", formData, {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      })
      .then((res) => {
        // navigate(`/detail/${res.data}`)
        console.log(res.data)
      })
      .catch((err) => {
        console.log(err);
      });


    // formData에 저장된 값 확인 하기  
    for (var key of formData.keys()) {
      console.log(key, formData.get(key), "👩");
    }
  };

  const handleTitleChange = (e) => {
    setTitle(e.target.value);
  };

  const handlePriceChange = (e) => {
    setPrice(e.target.value);
  };

  const handleAvailableTimeChange = (e) => {
    setAvailableTime(e.target.value);
  };

  const handleDescriptionChange = (e) => {
    setDescription(e.target.value);
  };

  

  return (
    <form className={styles.body} onSubmit={handleSubmit}>
      <div className={styles.nav}>
        <ChevronLeftIcon className="w-6 h-6 text-black-100" />
        <div className={styles.title}>상품 등록하기</div>
      </div>
      <div className={styles.container}>
        <div className={styles.button}>
          <CameraIcon className={styles.camera} />
          <div className={styles.num}>0/5</div>
        </div>

        <input
          className={styles.file}
          type="file"          // 파일로 입력 받음
          accept="image/*"     // 이미지 유형의 파일만 받기
          capture="camera"     // 모바일에서 직접 카메라가 호출될 수 있도록 하는,,,근데 이제,, 나는 안해본,,
          name="imgurls"       // 담긴 파일을 참조할 때 사용할 이름
          multiple            // 다중 업로드
        />

        <input
          className={`${styles.input} ${styles.titleinput}`}
          onChange={handleTitleChange}
          type="text"
          placeholder="제목"
        />
        <input
          className={styles.input}
          onChange={handlePriceChange}
          type="number"
          placeholder="$ 가격 (0원 가능)"
        />
        <textarea
          className={styles.textarea}
          onChange={handleAvailableTimeChange}
          placeholder="라이브 가능 시간 &#13;(ex - 10:00~12:00, 18:00~19:00)"
        ></textarea>
        <textarea
          className={`${styles.textarea} ${styles.descinput}`}
          onChange={handleDescriptionChange}
          placeholder="상품 설명(300자 이내)"
        ></textarea>
        <button type="submit" className={styles.addbtn}>
          <span>등록하기</span>
        </button>
      </div>
    </form>
  );
}

실행결과

- 콘솔창을 보면 값이 잘 append 된 것을 확인할 수 있다.

 

 

 

 

얼굴이 뜨거워 질 정도로 계속 에러가 났었던,,, 성공하자마자 백엔드님 팔뚝을 주먹으로 몇 대나 때려 버렸는지 모를,,

댓글