Toks를 피보팅하고 ver2가 마무리되어 가고 있던 중 ..
슬랙에 위와 같은 내용이 QA 채널에 올라왔다.
심하면 얼마나 심하겠어? 하고 확인해 봤는데 . 너무너무너무너무 거슬렸다. FE 개발자라면 참지 못할 정도로,,,
좋아요 버튼을 누르자마자 ui에 반영되지 않고 서버를 통해 업데이트를 진행하고 업데이트 된 데이터를 화면에 보여주어 딜레이가 발생하는 문제였다.
사용자 경험을 위해 optimistic update를 적용하는 것이 좋겠다고 판단이 되어 React Query를 통해 구현한 과정을 정리해보고자 한다.
기존 코드는 아래와 같다. 기존에 작성하던 mutation 코드와 별반 다르지 않다.
export const useLikeCommentMutation = (commentId: string, quizId: string) => {
const queryClient = useQueryClient();
const { mutate: likeComment, isLoading } = useMutation(
async () => {
try {
await postCommentLikeByCommentId({ commentId });
} catch {
throw new Error('댓글 좋아요 요청에 실패하였습니다.');
}
},
{
onSuccess: () => {
queryClient.invalidateQueries(QUERY_KEYS.GET_COMMENT_LIST(quizId));
},
}
여기서 잠깐,
QueryClient
는 invalidateQueries
메소드를 가지고 있다. queryClient.invalidateQueries()
와 같이 내부에 아무 쿼리 키도 작성하지 않으면 캐시에 있는 모든 쿼리를 invalidate하게 된다.exact: true
로 설정하면 된다.수정된 코드를 보면서 하나씩 따라가보자.
아래는 Tanstack Query를 이용해서 수정한 mutation 코드이다.
'use client';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QUERY_KEYS } from '@/app/quiz/constants/queryKeys';
import { postCommentLikeByCommentId } from '@/app/quiz/remotes/comment';
import { CommentListResponse } from '../models/comment';
export const useLikeCommentMutation = (commentId: string, quizId: string) => {
const queryClient = useQueryClient();
const { mutate: likeComment, isLoading } = useMutation(
async () => {
try {
await postCommentLikeByCommentId({ commentId });
} catch {
throw new Error('댓글 좋아요 요청에 실패하였습니다.');
}
},
{
onMutate: async () => {
await queryClient.cancelQueries(QUERY_KEYS.GET_COMMENT_LIST(quizId));
const previousLiked: CommentListResponse | undefined =
queryClient.getQueryData(QUERY_KEYS.GET_COMMENT_LIST(quizId));
queryClient.setQueryData(QUERY_KEYS.GET_COMMENT_LIST(quizId), () => {
return {
...previousLiked,
content: previousLiked?.content.map((el) => {
if (el.id === Number(commentId)) {
return {
...el,
isLiked: true,
likeCount: el.likeCount + 1,
};
} else {
return el;
}
}),
};
});
return { previousLiked };
},
onError: (err, previousLiked) => {
queryClient.setQueryData(
QUERY_KEYS.GET_COMMENT_LIST(quizId),
previousLiked
);
console.log(err);
},
onSettled: () => {
queryClient.invalidateQueries(QUERY_KEYS.GET_COMMENT_LIST(quizId));
},
}
);
return {
likeComment,
isLoading,
};
};
onMutate: async () => {
await queryClient.cancelQueries(QUERY_KEYS.GET_COMMENT_LIST(quizId));
const previousLiked: CommentListResponse | undefined =
queryClient.getQueryData(QUERY_KEYS.GET_COMMENT_LIST(quizId));
queryClient.setQueryData(QUERY_KEYS.GET_COMMENT_LIST(quizId), () => {
return {
...previousLiked,
content: previousLiked?.content.map((el) => {
if (el.id === Number(commentId)) {
return {
...el,
isLiked: true,
likeCount: el.likeCount + 1,
};
} else {
return el;
}
}),
};
});
return { previousLiked };
},
이제 진짜 Optimistic Update를 하기 위한 과정을 살펴보자.
onError: (err, previousLiked) => {
queryClient.setQueryData(
QUERY_KEYS.GET_COMMENT_LIST(quizId),
previousLiked
);
console.log(err);
},
onSettled: () => {
queryClient.invalidateQueries(QUERY_KEYS.GET_COMMENT_LIST(quizId));
},
optimistic update를 적용한 이후 클릭이 되자마자 ui에 좋아요 클릭 여부가 반영되는 것을 확인할 수 있다.
아주 속이 시원하다 !!!!
해당 로직과 관련된 전반적인 흐름은 아래 pr에서 확인할 수 있다.
https://github.com/depromeet/toks-web/pull/363
참고자료
https://tecoble.techcourse.co.kr/post/2023-08-15-how-to-improve-ux-with-optimistic-update/
https://tanstack.com/query/latest/docs/react/guides/optimistic-updates