이번에 코드리뷰 사이트에서 좋아요 기능을 구현해보았다. 기존에 구현한 방법은 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란?
요청을 보내기 전에 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] })
},
})
나는 요청이 성공하든 에러가 발생하든 실행하는 경우는 없어서 onError
와 onSuccess
, 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를 보면 누르자마자 바로 업데이트 되는 것을 확인할 수 있다.!
https://tanstack.com/query/v4/docs/react/guides/optimistic-updates
https://ghost4551.tistory.com/141
https://subtlething.tistory.com/m/127
오옹 잘보고 갑니다~