useQuery을 넘어 useMutation 사용

SeongHyeon Bae·2024년 1월 1일
2

문제상황

게시글의 댓글 작성 기능을 구현하고 있었습니다. 사용자가 현재 게시글에 댓글 등록 버튼을 누른 후, 새로고침을 해야만 새로운 댓글 정보를 서버로 부터 받아오는 문제가 발생하였습니다. 댓글 정보는 react query를 통해서 관리하고 있었기에 어떠한 방식으로 문제를 해결하고 개선하였는지 작성해보려 합니다.

react query 란?

TanStack Query 에 따르면

React Query는 종종 React에 없는 data-fetching 라이브러리로 되지만, 좀 더 기술적인 용어로 표현하자면 React 애플리케이션에서 서버 상태를 불러오고, 캐싱하고, 동기화하고, 업데이트하는 작업을 쉽게 해줍니다.

v4 부터 TanStack팀은 react 보다 좀 더 다양한 프레임워크에게 기능을 제공하여 react query대신 TanStack Query라는 표현을 사용하는것 같습니다.

이 라이브러리를 사용하는 이유

reDuck팀은 react query를 통해 로컬 데이터와 서버 데이터(게시글, 댓글) 로직을 분리하여 관리하기 위해 사용합니다. 또한, 서버 데이터를 캐싱하면서 서버에 데이터 요청 횟수를 최적화 할 수 있다는 장점이 있습니다.

문제의 원인

현재 댓글 상태 관리 flow는 다음과 같습니다.

  1. get 요청을 통해 현재 게시글의 댓글 list 들을 불러온다.
  2. post 요청을 통해 현재 게시글에 댓글을 추가한다.
  3. 새 댓글을 렌더링 하기 위해 1번을 다시 시작한다.

여기서 문제의 원인은 3번입니다. react-query에서는 서버의 상태(댓글 리스트)가 변했는지 알지 못하기에 개발자가 직접 알려주어야 합니다. 그러기 위해서 useQuery의 refetch 기능을 이용하여 post 요청 시 댓글 리스트 get 요청을 다시 하기로 하였습니다.

refetch를 이용한 코드

//ComentList.tsx
function ComentList() {
  const { data, refetch } = useQuery({
    queryKey: ['commentList'],
    queryFn: async () => await postManager.getPost(),
  });
  const comments = data?.comments

  return (
    <>
      <CommentUploadButton refetch={refetch}/>
      
        {comments?.map((comment: IComment) => (
          <Comment/>
        ))}
    </>
  );
}

//CommentUploadButton.tsx

function CommentUploadButton({refetch}) {
  const handleComment = async (content: string) => {
    await commentManager.createComment(content);
    refetch();
  };

  return <button onClick={handleComment}>등록</button>;
}

댓글 리스트가 필요한 컴포넌트에서 useQuery 훅을 사용하여 서버로 부터 데이터를 받고, 댓글 업로드 버튼에 Props 로 refetch를 전달하여 댓글 작성 완료 후 refetch를 실행하여 새로운 데이터를 렌더링 하였습니다.

refetch의 아쉬운 점과 개선

이러한 방식으로 해결은 하였지만 깔끔하게 해결된 느낌을 받지 못했습니다. 컴포넌트가 좀 더 복잡해지면 props drilling이 발생할 수 있고 refech를 해야하는 모든 경우에 메소드를 한번씩 적어야 하기 때문입니다.

useMutation을 사용하자.

공식문서에 따르면 데이터를 Get 요청할때에는 useQuery를 그 외(post,put,delete)의 경우는 useMutation을 권장합니다. useMutation 훅에서 제공되는 다양한 기능을 통해 좀 더 현명하게 데이터를 다룰 수 있습니다.

저는 여기서 댓글 작성 성공 시 기존 댓글 작성의 cache가 더이상 유효하지 않다고 알리고 데이터를 다시 받아오려고 합니다.

useMutation를 이용한 코드


//ComentList.tsx
function ComentList() {
  const { data } = useQuery({
    queryKey: ['commentList'],
    queryFn: async () => await postManager.getPost(),
  });
  const comments = data?.comments

  return (
    <>
      <CommentUploadButton />
      
        {comments?.map((comment: IComment) => (
          <Comment/>
        ))}
    </>
  );
}

//CommentUploadButton.tsx

function CommentUploadButton() {
  const queryClient = useQueryClient();
  const { mutate } = useMutation({
    mutationFn: (content: string) =>
      commentManager.createComment({ content}),
    onSuccess: () =>
      queryClient.invalidateQueries({ queryKey: ['commentList'] }),
  });
  
  const handleComment = () => {
    mutate(content);
  };

  return <button onClick={handleComment}>등록</button>;
}

코드를 요약하면 다음과 같습니다.

  1. useMutation 훅에 댓글 작성 함수를 mutationFn에 등록
  2. 성공시에 commentList 라는 쿼리키를 가진 쿼리가 더이상 유효하지 않다고 onSuccess에 등록
  3. 댓글 작성 시 mutation을 실행시킴.
  4. mutation이 성공한 뒤 queryClient.invalidateQueries를 실행 시켜 commentList에 해당하는 쿼리를 다시 받아옴

이를 통해 refetch 함수를 강제로 실행시키지 않아 CommentUploadButton 컴포넌트와 ComentList의 결합도를 낮추었습니다.

이러한 개선 과정을 통해 TanStack이 get이아닌 요청에 대해 useMutation을 쓰라고 권장하는 이유가 post이 요청되는 타이밍이 렌더링 시점이 아닌, 특정 이벤트를 받아 body값을 전달한다는 이유이지 않을까 조심스럽게 추론해 봅니다.

혹시 잘못된 부분은 댓글로 알려주시면 감사하겠습니다.🙇🏻‍♂️

profile
FE 개발자

0개의 댓글