[Tanstack Query] useMutation mutate와 mutateAsync (feat. React Hook Form)

Woonil·2025년 1월 24일
0

리액트 쿼리

목록 보기
3/4

프로젝트 리팩토링 중 게시판의 댓글을 작성하는 부분에서 버그가 있음을 발견했다. 버그는 다음과 같았다.

🤔문제

댓글을 작성하고 등록 버튼을 클릭하면 등록 성공과 함께 입력란이 공란으로 비워져야 하는데, 비워지지 않는 문제였다. 분명히 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 문법을 통해서 반환값의 상태에 따른 조건 분기를 해준다면 버그를 쉽게 해결할 수 있었다.

방법 1

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('댓글 작성 실패');
    },
  });
};

방법 2

에러 처리 로직을 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();
    },
  });
};
profile
프론트 개발과 클라우드 환경에 관심이 많습니다:)

0개의 댓글

관련 채용 정보