React Query Custom Hook 적용하기 (useMutation 편)

HYERI ·2023년 8월 13일
0

Spport 프로젝트

목록 보기
2/3
post-thumbnail

useMutation 적용

저번에 서버에서 데이터를 가져오는 용도로만 React-query를 사용했다면 이번에는 변경 요청을 보내는 작업을 처리해보록 할려고 한다.

useMutation를 사용하면, 서버에 데이터 변경 요청을 보내는 작업을 처리할 수 있다. 이때 해당 데이터 변경이 성공하면 새로운 데이터를 가져오거나 캐시를 업데이트하여 컴포넌트가 실시간으로 업데이트되도록 도와준다.

여기서는 댓글을 추가하고 삭제하는 hook을 useMutation을 활용해 만들었다.

hook: useComment

useCommentQuery

function useCommentQuery(token, postId) {
  const getComment = async () => {
    return await GET_API(token, `/post/${postId}/comments?limit=100`);
  };

  const commentQuery = useQuery({
    queryKey: ['comment'],
    queryFn: () => getComment(),
  });

  return [commentQuery.data, commentQuery.isLoading, commentQuery.isError];
}
  • useQuery를 사용해 댓글 정보를 가져온다. 여기서 queryKey는 comment로 정의해준다.

useAddCommentMutation

function useAddCommentMutation(token, postId) {
  const queryClient = useQueryClient();
  const addComment = async (content) => {
    const bodyData = {
      'comment': {
        'content': content,
      },
    };
    return await POST_API(token, `/post/${postId}/comments`, bodyData);
  };

  return useMutation((content) => addComment(content), {
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ['comment'],
        refetchType: 'active',
      });
    },
  });
}
  • useAddCommentMutation(token, postId): 토큰은 댓글을 추가하는 데 사용되며, 게시물 ID는 어느 게시물에 댓글을 추가할지 식별한다.
  • addComment(content):
    • 실제 댓글 추가 작업을 담당하는 함수.
    • content 매개변수로 받은 내용을 사용하여 POST 요청을 통해 서버에 새 댓글을 추가한다.
  • useMutation((content) => addComment(content), { ... }): useMutation 훅을 사용하여 댓글 추가 작업을 처리.
    • 첫 번째 매개변수로는 댓글 내용을 전달하는 함수 addComment를 전달한다
    • 두 번째 매개변수로는 옵션 객체를 전달한다.
    • onSuccess: 댓글 추가 작업이 성공한 경우, 이 콜백 함수가 실행된다.
      • 여기서는 쿼리를 업데이트하는 로직이 있습니다.
      • queryClient.invalidateQueries를 사용하여 'comment' 쿼리를 갱신하도록 지정한다.
      • refetchType을 'active'로 설정하여 활성화된 상태에서 재요청하도록 설정한다.
      • 이로써 새로운 댓글이 추가되면 'comment' 쿼리가 다시 요청되어 화면이 업데이트된다.

useDeleteCommentMutation

function useDeleteCommentMutation(token, postId, commentId) {
  const queryClient = useQueryClient();
  const deleteComment = async () => {
    return await DELETE_API(token, `/post/${postId}/comments/${commentId}`);
  };

  return useMutation(() => deleteComment(), {
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ['comment'],
        refetchType: 'active',
      });
    },
  });
}
  • useAddCommentMutation 과 비슷하게 작동하며 대신 매개변수를 받지 않아도 되며 성공시 deleteComment를 실행한다.
  • 댓글이 삭제되면 'comment' 쿼리가 다시 요청되어 화면이 업데이트된다.

React-query 도입 전 코드

component: InputComment

export default function InputComment({ image }) {
  const { id } = useParams();
  const [inputVal, setInputVal] = useState('');
  const [token, setToken] = useRecoilState(userToken);
  const [userImage, setUserImage] = useRecoilState(userimage);
  const handleSubmit = async (e) => {
    e.preventDefault();
    const postCmt = await writeCommentAPI(token, id, inputVal);
    setInputVal('');
    location.reload();
  };
  const handleInputChange = (e) => {
    setInputVal(e.target.value);
  };
  return (...);
}

  • 댓글을 추가하는 역할을 담당하는 컴포넌트
  • writeCommentAPI를 불러 댓글을 추가한다.
  • location.reload()를 불러 홈페이지를 reload한다.

component: ViewComment

export default function ViewComment({ comment, post_id }) {
  const profileLink = `/profile/${comment.author.accountname}`;
  const date = comment.createdAt;
  const displayDate = `${date.slice(0, 4)}.${parseInt(
    date.slice(5, 7),
  )}.${parseInt(date.slice(8))} ${date.slice(11, 13)}:${date.slice(14, 16)}`;

  const [token, setToken] = useRecoilState(userToken);
  const [accountName, setAccountName] = useRecoilState(accountname);
  const [isBsOpen, setIsBsOpen] = useRecoilState(isBottomSheetOpen);
  const [bsItems, setBsItems] = useRecoilState(bottomSheetItems);
  const [isModal, setIsModal] = useRecoilState(isModalOpen);
  const [modalItem, setModalItem] = useRecoilState(modalItems);

  const onMoreClick = () => {
    setIsBsOpen((prev) => !prev);
    if (accountName === comment.author.accountname) {
      const onCommentDelete = () => {
        setIsModal(true);
        const deleteComment = async () => {
          const data = await deleteCommentAPI(token, post_id, comment.id);
          setIsModal(true);
          setModalItem([
            '해당 댓글이 삭제되었습니다.',
            '확인',
            function () {
              location.reload();
            },
          ]);
        };
        setModalItem(['해당 댓글을 삭제할까요?', '삭제', deleteComment]);
      };
      const bsItem = [['삭제', onCommentDelete]];
      setBsItems(bsItem);
    } 
  };

  return (...);
}

  • 댓글 하나를 보여주는 컴포넌트, 자신의 댓글의 더보기 버튼을 누르면 삭제할 수 있다.
  • 모달과 바텀시트가 상당히 복잡하게 되어있지만 deleteComment 함수 안에서 deleteCommentAPI 를 사용해 댓글을 삭제하고 있다.

React-query 도입 후 코드

component: InputComment

export default function InputComment({ image }) {
  const { id } = useParams();
  const [inputVal, setInputVal] = useState('');
  const [token] = useRecoilState(userToken);
  const [userImage, setUserImage] = useRecoilState(userimage);
  const addCommentMutate = useAddCommentMutation(token, id);

  const handleSubmit = async (e) => {
    e.preventDefault();
    addCommentMutate.mutateAsync(inputVal);
    setInputVal('');
  };

  const handleInputChange = (e) => {
    setInputVal(e.target.value);
  };

  return (...);
}
  • writeCommentAPI 함수대신 addCommentMutation 훅을 사용해 댓글을 추가한다.
  • reload할 필요가 없어졌으므로 reload 함수를 삭제한다.

component: ViewComment

export default function ViewComment({ comment, post_id }) {
  // 일단 버튼을 누르면 modal이 나오도록 해둠
  // modal이 false면 안나오고 버튼을 누르면 setModal로 true로 바뀌며 모달 생성
  // 기본값은 false
  const profileLink = `/profile/${comment.author.accountname}`;
  const date = comment.createdAt;
  const displayDate = `${date.slice(0, 4)}.${parseInt(
    date.slice(5, 7),
  )}.${parseInt(date.slice(8))} ${date.slice(11, 13)}:${date.slice(14, 16)}`;

  const [token] = useRecoilState(userToken);
  const [accountName] = useRecoilState(accountname);
  const [isBsOpen, setIsBsOpen] = useRecoilState(isBottomSheetOpen);
  const [bsItems, setBsItems] = useRecoilState(bottomSheetItems);
  const [isModal, setIsModal] = useRecoilState(isModalOpen);
  const [modalItem, setModalItem] = useRecoilState(modalItems);
  const deleteCommentMutate = useDeleteCommentMutation(
    token,
    post_id,
    comment.id,
  );

  const onMoreClick = () => {
    setIsBsOpen((prev) => !prev);
    if (accountName === comment.author.accountname) {
      const onCommentDelete = () => {
        setIsModal(true);
        const deleteComment = async () => {
          await deleteCommentMutate.mutateAsync();
          setIsModal(true);
          setModalItem(['해당 댓글이 삭제되었습니다.', '확인', function () {}]);
        };
        setModalItem(['해당 댓글을 삭제할까요?', '삭제', deleteComment]);
      };
      const bsItem = [['삭제', onCommentDelete]];
      setBsItems(bsItem);
    } 
  };

  return (...);
}
  • deleteCommentAPI 함수 대신 useDeleteCommentMutation 훅을 사용해 댓글을 삭제한다.

효과

댓글을 추가하거나 삭제하면 서버에는 반영되지만 화면상에는 바로 반영되지 않아 일단은 페이지 리로드를 강제로 하는 location.reload() 함수를 추가했다. 하지만 댓글을 추가하거나 삭제할때마다 페이지 리로드하는게 비효율적이라고 느껴졌고 매번 스켈레톤 UI를 다시 보는 것이 사용자 경험에 좋지 않았다.

React-query 도입 후 페이지 리로드를 하지 않아도 서버에 반영된 데이터가 화면에도 바로 반영이 되어서 사용자 경험을 향상시켰다.

화면상 효과

댓글 추가 이벤트 (React-query 적용 전)댓글 추가 이벤트 (React-query 적용 후)
댓글 추가 후 페이지를 reload하지 않으면 화면 상에 바로 뜨지 않기에 댓글 추가 후 강제로 reload하도록 했다.댓글 추가 후 페이지를 reload하지 않아도 바로 추가 되는 것을 확인할 수 있다.

0개의 댓글