보통 이미지/오디오 업로드 시 formData를 사용해 API 요청을 하게 되는데,
백엔드에서 formData 안에 application/json
형식의 json data와 파일을 함께 넣어서 요청하는 방식으로 구현해 주셨다.
처음에 보고 읭 json data를 formData에 넣을 수 있다고? 싶었는데 알고 보니 방법이 있었다.
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files[]
if(file){
const data = { fileType : "abcde" } // 백엔드에서 요청한 json 예시
const formData = new FormData();
formData.append(
"data",
new Blob([JSON.stringify(data)], {
type: "application/json",
}) // application/json 형식으로 넣어 주기
);
// 하나의 파일
formData.append("image", file) // multipart/form-data 형식, 업로드할 파일 집어넣기
// ...API 요청
}
// ...
}
Blob을 사용해 첫번째 인수에 JSON.stringify()
로 변환해 준 데이터, 그리고 type을 application/json
로 지정하면 application/json 형식으로 json data를 보낼 수 있다.
파일 업로드 API가 여러 개의 파일 업로드가 가능하도록 구현되어 있는 경우이다. 어떻게 여러 개의 파일을 동시에 보낼 수 있는지 싶었는데 이것도 아주 간단한 방법이!
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
const formData = new FormData();
// 여러 개의 파일
Object.values(files).forEach((file) =>
if(type){
formData.append("files", file, file.name)
}
);
// ...API 요청
}
//...
}
Object 형식인 files의 values(file)만 가져와서 각 file마다 key name, file, file 이름을 formData에 담아 주면 끝!
이대로 마무리하기 아쉬워서...
백엔드 API가 만들어 지기 전 FileReader 객체로 화면에서 파일 관련 정보(이미지 미리보기, 오디오 정보 등) 확인해 볼 수 있도록 로직을 먼저 구현해 놓고 API를 나중에 적용하게 될 때의 경우이다.
setState로 값을 변경하는 방식은 동일하게 적용하되,
아예 업로드 API 요청 후 응답 데이터 값을 가져와 뿌려주도록 'file 이벤트 함수에서 useMutation
을 통한 API 요청 + setState를 통한 값 변경' 방식의 적용은 다음과 같이 구현할 수 있다.
(이전의 json Data 넣기, 여러 개의 파일 집어넣기까지 모두 포함해 보았다!)
변경 전
// ** 1) Recoil atom의 useSetRecoilState / 또는 useState 의 setState props 가져오기
// ** 2) POST API 요청을 위한 useMutate 훅 가져오기
const { mutate:audioUpload } = usePostAudioUpload();
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = (e) => {
const newAudio = new Audio(e.target?.result as string);
newAudio.onloadedmetadata = () => {
setData((prev) => {
// audio file과 재생시간 값 가져오기
const updatedData = prev.map((prevData, i) => {
if (index === i) {
return {
...prevData,
audio: file,
duration: newAudio.duration,
};
}
return data;
});
return updatedData;
});
};
};
}
변경 후
// ** 1) Recoil atom의 useSetRecoilState / 또는 useState 의 setState props 가져오기
// ** 2) POST API 요청을 위한 useMutate 훅 가져오기
const { mutate:audioUpload } = usePostAudioUpload();
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
const data = { fileType : "abcde" };
const formData = new FormData();
formData.append(
"data",
new Blob(data)], {
type: "application/json",
})
);
// 여러 개의
Object.values(files).forEach((file) =>
formData.append("files", file, file.name)
);
// 오디오 업로드 API 요청
audioUpload(formData, {
onSuccess: (data) => {
setData((prev) => {
const updatedData = prev.map((prevData, i) => {
if (index === i) { // 기존 data의 index 값과 비교
return {
...prevData,
audio: data[i].url,
duration: data[i].duration,
};
}
return audio;
});
return updatedData;
});
},
});
}
}