프로젝트 진행중 마이페이지 작업을 하는데 이미지 업로드에 대한 내용을 정리하려고 한다.

이미지 업로드 버튼을 누르면, 모달창이 뜨고, 해당 모달창 안에서 이미지 미리보기후 이미지를 업로드 하는 방식을 사용하려고 한다.
이전에 포스팅했던, 모달hook 만들기 를 사용해서 모달 부분을 구현했다.
const {isOpenModal, hanldeOpenModal} = useModal();
return (
<div>
<div onClick={handleModal}>이미지 업로드</div>
{isOpenModal && (
<Modal show={isOpenModal} hide={handleModal}>
<div>이미지 미리보기</div>
<form>
<input
style={{ display: 'none' }}
type="file"
id="profileImg">
</input>
<label htmlFor="profileImg">이미지 선택</label>
</form>
<div>업로드</div>
</Modal>
)}
</div>
업로드 버튼을 누르면, 모달이 켜지도록 했다.
모달 안쪽에서는 사진을 추가할 수 있고, 추가한 사진을 확인할 수 있도록했다.

const {isOpenModal, hanldeOpenModal} = useModal();
const [uploadImage, setUploadImage] = useState<FormData>();
return (
<div onClick={handleModal}>이미지 업로드</div>
{isOpenModal && (
<Modal show={isOpenModal} hide={handleModal}>
<div>이미지 미리보기</div>
{uploadImage.url ? (
<div style={{ width: 100, height: 100 }}>
<img
src={uploadImage}
alt="업로드프로필미리보기"
style={{ width: 100, height: 100 }}
/>
</div>
) : (
<div style={{ width: 100, height: 100 }}></div>
)}
<form>
<input
style={{ display: 'none' }}
type="file"
id="profileImg"
onChange={onChangeImageUpload}></input>
<label htmlFor="profileImg">이미지 선택</label>
</form>
<div onClick={modifedMyImage}>업로드</div>
</Modal>
)}
);
const [uploadImage, setUploadImage] = useState<FormData>(); //이미지를 저장하기위한 상태
const onChangeImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) {
return;
}
const formData = new FormData(); //FormData객체 생성
formData.append('image', e.target.files[0]); //생성한 FormData에 image추가
setUploadImage(formData);
};
const modifedMyImage = async () => {
if (confirm('업로드하시겠습니까?')) {
uploadImage && (await USERS.modifedUserImage(uploadImage));
}
};
return (
<div>
<div onClick={handleModal}>이미지 업로드</div>
{isOpenModal && (
<Modal show={isOpenModal} hide={handleModal}>
<div>이미지 미리보기</div>
<form>
<input
style={{ display: 'none' }}
type="file"
id="profileImg"
onChange={handleImageUpload}></input>
<label htmlFor="profileImg">이미지 선택</label>
</form>
<div onClick={modifedMyImage}>업로드</div>
</Modal>
)}
</div>
상태를 하나 추가해서, 이미지를 업로드하고, 해당 이미지값을 FormData형식에 저장한 후 useState를 통해서 값을 저장시켰다.
이미지 미리보기를 위해서 blob을 사용했다.
Blob 객체는 파일류의 불변하는 미가공 데이터를 나타냅니다. 텍스트와 이진 데이터의 형태로 읽을 수 있으며, ReadableStream으로 변환한 후 스트림 메서드를 사용해 데이터를 처리할 수도 있습니다.
Blob은 JavaScript 네이티브 형태가 아닌 데이터도 표현할 수 있습니다. File이 Blob에 기반한 인터페이스로, 사용자 시스템의 파일을 지원하기 위해 Blob 인터페이스를 상속해 기능을 확장한 것입니다.
reference
미리보기를 하기 위해서 기존에 있던 상태값을 조금 변경해 주었다.
const [uploadImage, setUploadImage] = useState<FormDataType>({
object: {} as File,
url: '',
});
실제 이미지 File을 담을 object 상태ojbect : {} 와
blob형태의 file url로 바꿨을 때 담는 상태 url : "" 를 만들었다.
매 번 값이 유지되면 안되기 때문에 useEffect를 통해서 초기값을 할당해주었다.
useEffect(()=>{
setUploadImage({
object: {} as File,
url : '',
});
이미지 미리보기는 선택한 값에 따라서 달라져야 하기 때문에 onChange가 작동할 때 blob객체를 url로 바꾸는 작업을 넣어주었다.
실제 컴퓨터에서 받아온 파일을 URL.createObjectURL()을 사용해서 url형태로 바꿔주었다.
const onChangeImageUpload = async (
e: React.ChangeEvent<HTMLInputElement>,
) => {
if (!e.target.files) {
return;
}
setUploadImage({
object: e.target.files[0],
url: URL.createObjectURL(e.target.files[0]),
});
};
setuploadImage에 ojbect에는 실제로 들어갈 이미지 파일을 담아주었고, url에는 blob을 url형태로 바꾼 값을 담아주었다.
이미지 업로드 버튼을 클릭했을 시, FormData객체에 state로 저장해둔 값을 담고, 백엔드에 api요청을 보냈다.
const modifedMyImage = async () => {
const formData = new FormData();
formData.append('image', uploadImage.object);
if (uploadImage.url !== '') {
if (confirm('업로드하시겠습니까?')) {
await USERS.modifedUserImage(formData); //폼 데이터를 담아서 보냄
}
} else {
alert('이미지 등록이 필요합니다.');
}
};
api요청을 보내는 부분이다.
async modifedUserImage(image: FormData): Promise<any> {
const result: AxiosResponse = await instance.patch(
`${USERS.path}/image`,
image,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
);
return result.data;
},
헤더 부분에 form-data를 담아준 다는 것을 명시해주었다.
이렇게 하면 이미지가 잘 업로드 된는줄 알았다...

백엔드로 데이터를 보내는 중에 에러가 발생했다.

뭐지! 분명 이미지도 잘 담아보내고! accessToken도 잘 담아서 보냈다...
결론적으로 말하자면, 위에 이미지 부분이 잘못된 것이다...
나는 이미지를 보낼 때 image: File(이미지파일) 형태로 보냈는데, 백엔드에서 받을때, file:File형태로 받는 것이었다.........
그래서 FormData에 담을 때, 이름을 file로 변경해 주었다.
const modifedMyImage = async () => {
const formData = new FormData();
formData.append('file', uploadImage.object);
if (uploadImage.url !== '') {
if (confirm('업로드하시겠습니까?')) {
await USERS.modifedUserImage(formData); //폼 데이터를 담아서 보냄
}
} else {
alert('이미지 등록이 필요합니다.');
}
};
그랬더니 잘 작동한다!

const MyPageUserInfo = ({ id }: UserType) => {
const { isOpenModal, handleModal } = useModal();
const [getUserInfo, setUserInfo] = useState<UserInfoType>({
id: 0,
name: '',
email: '',
admin: false,
provider: '',
userImage: '',
owner: false,
});
const [uploadImage, setUploadImage] = useState<FormDataType>({
object: {} as File,
url: '',
});
const userInfoApi = async () => {
if (id !== 0) {
const response = await USERS.getUserProfile(id);
setUserInfo(response);
}
};
useEffect(() => {
setUploadImage({
object: {} as File,
url: '',
});
userInfoApi();
}, [id]);
const onChangeImageUpload = async (
e: React.ChangeEvent<HTMLInputElement>,
) => {
if (!e.target.files) {
return;
}
setUploadImage({
object: e.target.files[0],
url: URL.createObjectURL(e.target.files[0]),
});
};
const modifedMyImage = async () => {
const formData = new FormData();
formData.append('file', uploadImage.object);
if (uploadImage.url !== '') {
if (confirm('업로드하시겠습니까?')) {
await USERS.modifedUserImage(formData);
}
} else {
alert('이미지 등록이 필요합니다.');
}
};
useEffect(() => {
console.log(uploadImage);
}, [onChangeImageUpload]);
return (
<S.FlexBox>
{getUserInfo.owner ? (
<div>
<S.UserImgBox src={getUserInfo.userImage} alt="유저사진" />
<div onClick={handleModal}>이미지 업로드</div>
{isOpenModal && (
<Modal show={isOpenModal} hide={handleModal}>
<div>이미지 미리보기</div>
{uploadImage.url ? (
<div style={{ width: 100, height: 100 }}>
<img
src={uploadImage.url}
alt="업로드프로필미리보기"
style={{ width: 100, height: 100 }}
/>
</div>
) : (
<div style={{ width: 100, height: 100 }}></div>
)}
<form>
<input
style={{ display: 'none' }}
type="file"
id="profileImg"
onChange={onChangeImageUpload}></input>
<label htmlFor="profileImg">이미지 선택</label>
</form>
<div onClick={modifedMyImage}>업로드</div>
</Modal>
)}
</div>
) : (
<S.UserImgBox src={getUserInfo.userImage} alt="유저사진" />
)}
<S.UserInfoBox>
<div>{getUserInfo.name}</div>
<div>{getUserInfo.email}</div>
</S.UserInfoBox>
</S.FlexBox>
);
};
export default MyPageUserInfo;