사용자가 데이터 변경 작업을 수행했을 때, 실제 서버 응답을 기다리지 않고 UI를 즉시 업데이트 하는 방법이다.
- 사용자가 데이터를 변경하는 작업 수행
- UI에서 변경된 결과를 즉시 반영
- 서버에 변경 요청을 전송
- 서버 응답이 성공인 경우, UI 유지
- 서버 응답이 실패인 경우, 이전 상태로 롤백하거나 에러처리
export const useSidebarGoalsMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: postSidebarGoals,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SIDEBAR_GOALS] });
queryClient.invalidateQueries({
queryKey: [QUERY_KEYS.TODOS_OF_GOALS],
});
},
onError: (error) => {
console.error(error.message);
notify('error', '목표 등록에 실패했습니다.', 3000);
},
});
};
기존 코드에 낙관적 업데이트를 적용하기 위해서
onMutate을 작성하고 onSuccess 대신 성공, 실패 여부에 관계 없이 실행하기 위해 onSettled를 작성
그에 맞는 onError 코드도 변경하였다.
export const useSidebarGoalsMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (postData: PostGoalTypes) =>
POST<GoalsResponse, PostGoalTypes>(API_ENDPOINTS.GOAL.GOALS, postData),
onMutate: async ({ title }) => {
const prev = queryClient.getQueriesData({ queryKey: [QUERY_KEYS.GOALS] });
await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.GOALS] });
queryClient.setQueryData([QUERY_KEYS.GOALS], (oldData: GoalsResponse) => {
if (!oldData?.data) return oldData;
return {
...oldData,
data: [
...oldData.data,
{
goalId: Date.now(),
goalTitle: title,
color: '#848484',
createAt: new Date().toISOString(),
},
],
};
});
return { prev };
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GOALS] });
queryClient.invalidateQueries({
queryKey: [QUERY_KEYS.TODOS_OF_GOALS],
});
},
onError: (error, newGoal, context) => {
queryClient.setQueriesData(
{ queryKey: [QUERY_KEYS.GOALS] },
context?.prev,
);
console.error(error.message);
notify('error', `목표 등록에 실패했습니다.\n ${newGoal.title}`, 3000);
},
});
};
서버에 요청을 보내기 전에 실행한다.
해당 쿼리의 기존 데이터를 prev
로 저장
cancelQueries
로 목표 관련 쿼리를 중단해 기존 데이터와 서버 응답의 충돌을 방지
기존 값 (oldData
) 에 새로운 데이터를 추가해 UI를 즉시 갱신
prev
를 리턴해준다.
서버 요청이 성공하거나 실패한 후에 실행한다.
onSettled로 관련된 쿼리 키들을 invalidate하여 최신 데이터를 가져오도록 함
서버의 응답이 실패하면 실행한다.
onMutate
에서 저장한 prev
를 사용해 쿼리를 복구한다.
에러 메시지를 통해 사용자에게 알린다.