프로젝트 리팩토링 중 게시판의 댓글을 작성하는 부분에서 버그가 있음을 발견했다. 버그는 다음과 같았다.
댓글을 작성하고 등록 버튼을 클릭하면 등록 성공과 함께 입력란이 공란으로 비워져야 하는데, 비워지지 않는 문제였다. 분명히 react hook form 에 'comment'로 등록된 methods.reset()
을 호출했는데도 정상적으로 입력란 초기화 동작이 수행되지 않은 이유는 뭐였을까?
// 댓글 작성
const handleCommentSubmit: SubmitHandler<INewCommentValues> = ({ comment }) => {
createComment(comment);
if (isSuccess) {
methods.reset();
}
};
사실, 이유는 간단했다. 또다시 비동기 처리가 문제가 되었던 것이다. mutate 동작을 수행하는createComment()
는 useMutation
동작을 수행하는 useCreateComment
라는 함수에서 반환하는 값이다. useMutation
내부적으로는 서버에 댓글 등록 요청을 보내는 비동기 작업이 진행된다. 그렇기에 동기적으로 코드를 수행하는 mutate
를 사용하는 것은 잘못된 방법이었던 것이다. 동기적으로 진행된 코드는 아직 false
값인 isSuccess
를 만나고는 입력란 초기화 작업을 수행하는 코드를 수행하지 못하였다. 따라서, 이를 방지하기 위해서는 mutate
가 아닌 mutateAsync
를 사용하여 비동기적인 처리를 해주어야 한다.
공식문서에 mutateAsync
는 void를 반환하는 mutate
와 다르게 Promise
객체를 반환하도록 설계되어 있다. 따라서 async & await
문법을 통해서 반환값의 상태에 따른 조건 분기를 해준다면 버그를 쉽게 해결할 수 있었다.
const { mutateAsync: createComment, ... } = useCreatePhotoAlbumComment();
// 댓글 작성
const handleCommentSubmit: SubmitHandler<INewCommentValues> = async ({ comment }) => {
const res = await createComment(comment);
// 분기 처리 예시
if (res.status === 200) {
methods.reset(); // form 리셋
}
};
하지만, 리액트 쿼리는 이러한 비동기 작업의 결과를 mutate
를 통해서도 간편하게 처리할 수 있는 기능을 제공한다. 바로 콜백함수인 onSuccess
, onError
, onSettled
를 사용해서 말이다. 사실 이미 useMutation
선언을 하는 useCreateComment
함수에서도 onSuccess
에 대한 처리가 되어있다. 하지만, 해당 함수는 리액트 훅 폼의 useForm
을 사용하는 컴포넌트와 분리되어 있었으므로, 리액트 훅 폼의 reset
을 넘겨주기는 쉬운 작업이 아니었다. 따라서, mutate()
의 두번째 인자로 결과 처리를 진행해주어 핸들러 함수 내에서 폼 초기화 작업을 진행할 수 있었다.
// 댓글 작성
const handleCommentSubmit: SubmitHandler<INewCommentValues> = async ({ comment }) => {
createComment(comment, {
onSuccess: () => {
methods.reset(); // form 리셋
},
onError: () => {
console.log('댓글 작성 실패');
},
});
};
에러 처리 로직을 useMutation을 수행하는 useCreatePhotoAlbumComment에서 일괄적으로 처리할 수 있게 분리하는 것이 단일 책임 원칙에 더 적합할 것 같아서 재수정하였다.
const methods = useForm({
mode: 'onBlur',
});
const { mutate: createComment, isPending: isCommentPending } = useCreatePhotoAlbumComment(methods.reset);
// HANDLER: 댓글 작성
const handleCommentSubmit: SubmitHandler<INewCommentValues> = async ({ comment }) => {
createComment(comment);
};
---
// POST: 댓글 작성
const useCreatePhotoAlbumComment = (resetForm: () => void) => {
return useMutation({
mutationFn: async (newComment: string) => {
// api 요청 로직
},
// 클라이언트 업데이트
onSuccess: () => {
// ...
resetForm();
},
});
};