나는,, 상품의 정보를 등록할 때, 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="라이브 가능 시간 (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 된 것을 확인할 수 있다.
얼굴이 뜨거워 질 정도로 계속 에러가 났었던,,, 성공하자마자 백엔드님 팔뚝을 주먹으로 몇 대나 때려 버렸는지 모를,,
'Project > zum:go' 카테고리의 다른 글
[React] react-Intersection-Observer 라이브러리를 이용해 무한스크롤 구현하기 (2) | 2023.02.07 |
---|---|
[React] 타이머 기능 구현하기 - setInterval (0) | 2023.02.05 |
[React] 실시간 채팅 구현하기 - STOMP (0) | 2023.02.03 |
[React] swiper로 이미지 슬라이드 구현하기 (0) | 2023.01.28 |
[React] 카카오 로그인 (0) | 2023.01.26 |
댓글