모임 일정 관리 서비스 프로젝트를 진행하면서 겪었던 어려움, 고민 과정을 기록하고자 한다.
깃허브 주소: https://github.com/prgrms-web-devcourse/Team-5YES-WuMo-FE 배포 주소: https://5yes-wumo.vercel.app/ (회고 주소 추가 예정) |
프로젝트를 진행하면서 댓글을 생성/수정에서 이미지 업로드 기능을 구현해야 했다.
이미지를 서버에 등록하는 api와 댓글 생성 api가 나뉘어 있어서 댓글을 작성할 때 글과 이미지를 선택해서 댓글 생성 버튼 클릭 하면
이미지 등록 api 호출 -> 이미지 url 반환 -> 반환된 이미지 url로 댓글 생성 api 호출
이런 순서로 진행되어야 했기 때문에 이미지 등록 api가 완료될 때까지 기다린 후에 댓글 api가 호출되어야 했다. 따라서 async/await를 통해 관리해주어야겠다고 생각하였다.
첫 시도
const {
data,
mutate: createImageUrl,
reset: imageReset,
} = useMutation(createImage);
...
const compressedImageFile = await createImageUrl(image); //image url 생성 api 호출
...
코드처럼 mutate를 호출하면서 await를 걸었을 때
'await' has no effect on the type of this expression.
이런 문구가 뜨면서 async/await의 실행이 안 됐었다. 코드 로직의 문제라기 보단 useMutation에 대한 이해가 부족해서 일어난 문제인 것 같아 공식문서를 찾아봤다.
공식문서에서 useMutation을 찾아보니
mutation의 반환 타입은 void 였다. async/await는 Promise일 때 적용되기 때문에 적용이 안 되는 것이 당연했다. 이 사실을 알고 아까의 문구를 다시 읽어보니 이해가 됐었다.
그러면 useMutation에서 async/await를 사용할 수 있는 방법은 없을까 하고 공식문서를 더 찾아보면서 mutateAsync를 발견했다.
mutateAsync는 mutate와 비슷하지만 promise를 사용하기 때문에 await를 사용할 수 있는 값이었다. 그래서 바로 이 값을 코드에 적용해주었다.
두 번째 시도
const {
data,
mutateAsync: createImageUrl,
reset: imageReset,
} = useMutation(createImage);
...
const onSubmitNewComment = async ({ content, image }: CommentCreateType) => {
const imageUrl = await createImageUrl(image); //이미지 url 생성
...
//댓글 생성 api 호출
console.log(data);
}
mutateAsync를 사용하면서 await가 적용되어 해당 api 호출이 성공할 때까지 순서를 기다렸다가 코드가 실행된다.
await는 적용되었으나 또 다른 문제는 data가 undefined이라는 것이었다. 내가 예상한 로직은 api호출에 성공했을 때 반환되는 이미지 url이 useMutation의 data로 할당되는 것이었다. 하지만 async/await로 api 호출에 성공한 뒤임에도 data는 초기값인 undefined가 반환되었다.
data가 undefined인 이유는 리액트에서 useState에서 state값을 변경하고 바로 적용되지 않는 것과 이유가 같다.
const [count, setCount] = useState(undefined)
<button onClick={() => {
setCount(1)
console.log(count)
}) />
위의 코드에서 console.log(count)를 했을 때 undefined가 나오는 것과 똑같이 해당 이벤트가 끝나고 다음 렌더링 때 해당 값을 갱신하기 때문이다. 따라서 api 호출이 끝난 뒤에 바로 data를 호출한다고 해도 undefined 인 것이다.
그래서 api 호출에 성공했을 때 실행되는 onSuccess 옵션을 사용해서 imageUrl을 state로 저장하는 방법을 적용해 보았다.
세 번째 시도
const [imgUrl, setImgUrl] = useState(undefined);
const { mutateAsync: createImageUrl, reset: imageReset } = useMutation(createImage, {
onSuccess: (data) => {
setUrl(data);
return data;
},
});
onSuccess를 이용해서 통신이 성공한 후에 할당해 둔 state의 값을 업데이트한다. 그리고 imgUrl을 사용해서 댓글 생성 api를 호출한다. 이 방법은 성공적으로 api 호출 후 반환된 이미지 url를 사용할 수 있었다. 하지만 이를 위해 useState를 새로 할당해야 한다는 것이 마음에 걸렸다. 그래서 useState를 사용하지 않고 이미지 생성 함수를 따로 만들어 반환 값을 이용해서 진행하기로 하였다.
✅사용한 방법
기존에는 한 함수 안에서 이미지생성 api를 호출한 다음, data를 이용해서 댓글 생성 api를 호출하려고 했었다. 그런데 방향을 바꾸어서 이미지 url을 호출하는 함수를 따로 만들고 해당 함수에서 image url을 return 한다. 그리고 그 리턴 값을 활용해서 댓글 생성 api를 호출하는 것으로 수정하였다.
기존에 data를 사용하려고 했던 방법
const {
data,
mutateAsync: createImageUrl,
reset: imageReset,
} = useMutation(createImage);
const onSubmitNewComment = async ({ content, image }: CommentType) => {
if (!image) return null;
const formData = new FormData();
formData.append('image', image);
await createImageUrl(formData);
const commentBody: CreateCommentBody = { //댓글 생성 api body 만들기
content,
image: data,
partyId: Number(partyId),
locationId: state.locationId,
};
...
}
✅함수를 분리해서 반환값을 이용하도록 수정한 방법
const { mutateAsync: createImageUrl, reset: imageReset } = useMutation(createImage);
const onSubmitImageFile = async (image: File | null) => {
if (!image) return null;
const formData = new FormData();
formData.append('image', image);
const imageUrl = await createImageUrl(formData);
return imageUrl;
};
const onSubmitNewComment = async ({ content, image }: CommentType) => {
const imageUrl = await onSubmitImageFile(image);
const commentBody: CreateCommentBody = {
content,
image: imageUrl,
partyId: Number(partyId),
locationId: state.locationId,
};
...
}
useMutation의 data도 결국 리액트의 작동원리와 똑같다는 걸 몰라서 더 헤맸던 것 같다. 리액트의 작동원리에 대해 다시 공부해야겠다는 생각이 들었고 data를 잘 활용한 사례는 무엇이 있을지 찾아보고 싶어졌다. react, react query 도 더 깊이 공부해봐야 할 것 같다.
해당 소스코드 전체 보기:
'개발 공부 > React' 카테고리의 다른 글
color-thief 라이브러리 type 지정하기 (0) | 2023.07.02 |
---|---|
[프로젝트 고민]input 불필요한 렌더링 줄이기 (0) | 2023.04.15 |
input type='file' 동일 파일 재 선택 시 오류 (0) | 2023.03.22 |
리액트 429 에러 해결(useEffect 무한루프) (1) | 2022.10.05 |
React - Fragment 사용하기 (0) | 2022.09.19 |