좋아요 기능 구현하기 (with Optimistic Update)

:D ·2023년 4월 2일
2
post-thumbnail

기존 방법

이번에 코드리뷰 사이트에서 좋아요 기능을 구현해보았다. 기존에 구현한 방법은 likeQuestion api가 성공하면 다시 question api를 불러 빈하트 -> 꽉찬 하트 (좋아요가 된 이미지)를 보여주는 방식이었다.

export const useLikeQuestionMutation = ({ onSuccess, onError }: MutationCallbacks = {}) => {
  const queryClient = useQueryClient();
  return useMutation(likeQuestion, {
    onSuccess: () => {
      onSuccess && onSuccess();
      queryClient.invalidateQueries([KEYS.QUESTION]);
    },
    onError: (error: CustomError) => {
      onError && onError();
    },
  });
};

아래의 gif들은 네트워크 속도를 점점 느리게 조절해보았다.
그 결과 네트워크가 느릴 수록 서버로부터 응답을 받기 전까지 아무런 반응이 없어 사용자 경험에 좋지 않은 것을 볼 수 있다.

그러면 어떻게 개선할 수 있을까? 😲 optimistic update을 활용해보자!

optimistic update란?

요청을 보내기 전에 UI를 업데이트 하는 것이다.
사용자가 입력하면 바로 화면이 갱신이 되고 서버에 요청을 한 후, 요청이 실패하면 롤백 처리를 해줘야한다.

react query를 사용하여 optimistic update을 구현하는 방법은 React Query 공식문서에 잘 나와있다.

공식문서에 나와있는 단일 할 일 업데이트의 예로 살펴보면,

useMutation({
  mutationFn: updateTodo,
  // mutataion 함수가 실행되기 전에 실행 
  onMutate: async (newTodo) => {
    // 쿼리 요청을 취소하여 overwrite 하지 않도록 하기
    await queryClient.cancelQueries({ queryKey: ['todos', newTodo.id] })

    // 이전의 값을 저장해줌
    const previousTodo = queryClient.getQueryData(['todos', newTodo.id])

    // new value로 업데이트 해줌
    queryClient.setQueryData(['todos', newTodo.id], newTodo)
    return { previousTodo, newTodo }
  },
  // 에러가 발생한 경우 
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(
      ['todos', context.newTodo.id],
      context.previousTodo,
    )
  },
  // 요청이 성공하든 에러가 발생하든 실행 
  onSettled: (newTodo) => {
    queryClient.invalidateQueries({ queryKey: ['todos', newTodo.id] })
  },
})

개선된 방법

나는 요청이 성공하든 에러가 발생하든 실행하는 경우는 없어서 onErroronSuccess, onMutate 경우로 나누어서 구현하였다.

export const useLikeQuestionMutation = () => {
  const queryClient = useQueryClient();
  return useMutation(likeQuestion, {
    onSuccess: () => {
      queryClient.invalidateQueries([KEYS.QUESTION]);
    },
    onMutate: (questionId) => {
      queryClient.cancelQueries([KEYS.QUESTION, { questionId }]);
      const snapshotOfPreviousQuestion = queryClient.getQueryData<Question>([KEYS.QUESTION, { questionId }]);

      if (snapshotOfPreviousQuestion) {
        queryClient.setQueryData([KEYS.QUESTION, { questionId }], () => {
          return { ...snapshotOfPreviousQuestion, isLike: true, likeCount: snapshotOfPreviousQuestion.likeCount + 1 };
        });
      }
      return () => queryClient.setQueryData([KEYS.QUESTION, { questionId }], snapshotOfPreviousQuestion);
    },
    onError: (error, values, rollback) => {
      rollback && rollback();
    },
  });
};

아래의 gif를 보면 누르자마자 바로 업데이트 되는 것을 확인할 수 있다.!

References

https://tanstack.com/query/v4/docs/react/guides/optimistic-updates
https://ghost4551.tistory.com/141
https://subtlething.tistory.com/m/127

profile
강지영입니...🐿️

1개의 댓글

comment-user-thumbnail
2023년 6월 22일

오옹 잘보고 갑니다~

답글 달기