useMutation
훅의 옵션 중 하나인 onMutate
에 대한 정리.우선, onMutate
는 mutation을 시작하기 직전
에 호출되는 콜백함수이다.
데이터를 실시간으로 변경하는 것처럼 보이게 하려면, 서버의 응답을 기다리지 않고 클라이언트쪽에서 UI를 먼저 업데이트할 수 있다. 만약 서버에서 오류가 응답되면 원래 상태로 롤백하는 작업을 수행한다.
getQueryData를 사용하여 현재 캐싱된 데이터를 가져오는데, mutation이 실패한 경우 원래 상태로 롤백하는데 사용될 수 있다. onError 옵션과 함께 사용된다.
아래의 코드에서 originalTodos
를 반환하고 있는데, 이 반환 객체는 react query의 context
로 전달된다. context는 여러 옵션들 (onSuccess, onError, onSettled)의 콜백에서 첫번째 인자 다음에 제공된다. onError에서는 mutation이 실패했을 경우에 대한 콜백이 실행되므로 원래 캐싱된 데이터로 롤백하기 위해
와 같이 작성해준다.
onMutate: (newTodo) => {
// todos라는 쿼리키를 이용, 원래 상태 저장
const originalTodos = queryClient.getQueryData('todos');
// 새 todo 항목을 추가하여 클라이언트 상태 업데이트
queryClient.setQueryData('todos', old => [...old, newTodo]);
// 원래 상태 반환하여 필요한 경우 롤백할 수 있게 함
return { originalTodos };
}
onMutate: () => {
queryClient.cancelQueries('todos');
}
onMutate: (values) => {
if(!isValid(values)) {
throw new Error('Invalid data');
}
}
cancelQuery
를 통해 진행중인 쿼리를 취소함으로써, mutation 동안 다른 요청에 의한 변경을 방지한다.getQueryData
를 사용하여 현재 캐싱된 데이터를 가져온다.setQueryData
를 사용하여 optimistic update를 수행한다.👉 만약 순서가 바뀌어서 cancelQuery 전에 getQueryData와 setQueryData를 호출한다고 가정해 본다면, 그 사이 다른 요청이 발생할 경우 데이터 불일치 문제가 발생할 수 있다.
import { useMutation } from 'react-query';
import client from '@/lib/client';
import { queryClient } from '@/lib/react-query';
import { RecommendDto } from '@/model/models';
export const updateToolRecommends = (toolRecommend: RecommendDto) => {
return client.put(`/api/v2/admin/recommends`, toolRecommend);
};
export const useUpdateToolRecommends = (id: string) => {
const queryKey = ['recommends', id];
return useMutation({
mutationKey: ['update-toolRecommend'],
mutationFn: updateToolRecommends,
async onMutate(newUser) {
// mutation이 성공적으로 실행될것이라 가정하고 현재 쿼리 데이터를 수정했기 때문에
// 현재의 mutation과의 충돌을 방지하기 위해 cancleQueries를 이용해 쿼리를 취소해준다.
1️⃣await queryClient.cancelQueries(queryKey);
// recommends 쿼리키로 캐싱되어있는 데이터를 가져온다.
// prev는 mutation이 실패했을 때 원래의 경우로 롤백하기위해 사용된다. (onError)
// mutation 이전의 값을 가져와 context로 이전시킨다.
2️⃣const prev = queryClient.getQueryData(queryKey);
// optimistic update를 위해 쿼리 데이터를 임시로 수정된 값으로 업데이트 한다.
3️⃣queryClient.setQueriesData(queryKey, old => ({
...(old ?? {}),
...newUser,
}));
// 업데이트 이전의 값이 담긴 context 객체를 return 한다.
return { prev };
},
onSuccess: () => {
toast.success({ message: '수정하였습니다.' });
},
// 만약 mutate 작업이 실패했을 경우, 이전에 저장했던 값으로 다시 롤백시킨다.
onError: (err: any, __, context: any) => {
toast.error({
message: err.message ?? '실패하였습니다',
description: '다시 시도해주세요.',
});
if (context?.prev) queryClient.setQueryData(queryKey, context.prev);
},
// mutation의 성공 혹은 실패 여부와 관계없이 mutation 이후에 쿼리를 stale하게 만들어 refetch를 유도한다.
onSettled() {
queryClient.invalidateQueries(queryKey);
},
useErrorBoundary: false,
});
};
recommends
라는 쿼리키에 캐싱된 데이터)를 가져온다.prev
로 반환되고, 이는 mutation이 실패할경우(onError
) 원래의 상태로 롤백하기 위해 사용된다. 실패
했을 때 호출된다.context?.prev
를 통해 setQueryData
를 사용하여 캐시의 데이터를 원래 상태로 롤백한다.이전 상태의 데이터
가 계속 보이도록 하는것이다.모든 쿼리를 취소
한다.mutation이 발생하기 전
에 현재 진행중인 쿼리를 중단하여 UI가 일관성을 유지하도록 한다.예를 들어 사용자가 어떤 항목을 수정하려고 할때, 수정 작업이 수행되기 전에 해당 항목의
데이터를 가져오는
쿼리가 진행중이라면, 그 쿼리를 취소하여 데이터의 일관성을 보장할 수 있는 것이다.
상관없이
끝난 뒤에 호출된다.invalidateQueries
를 호출하여 recommends
쿼리를 무효화
한다.즉, onMutate 안에서 cancelQueries를 통해 mutation이 발생하기 전에 해당 쿼리를 취소하고, onSettled 안에서 invalidateQueries를 사용하여 mutation이 완료된 이후에 해당 쿼리를 무효화하여 최신 데이터를 다시 가져올 수 있도록 하는것이다!
optimistic update
: 사용자에게 mutation의 결과를 즉시 반영하는것처럼 보이게 하려면 (실제로는 아직 서버의 응답을 받지 않았지만) onMutate에서 캐시를 일시적으로 업데이트할 수 있다.