낙관적 업데이트는 사용자 인터페이스를 더 빠르게 반응하게 만들기 위해, 서버의 응답을 기다리지 않고 UI를 미리 업데이트하는 기법입니다. React Query에서는 onMutate 옵션을 사용하여 낙관적 업데이트를 구현할 수 있습니다. 다음은 useUpdateTaskStatus 훅에 낙관적 업데이트를 추가하는 방법입니다.



// 할일 완료 UI
<CheckableText
isChecked={task.doneAt !== null}
onChange={() => {
handleCheckBoxChange(task.id, !task.doneAt);
}}
>
// 할일 완료 핸들링 함수
const handleCheckBoxChange = (taskId: number, done: boolean) => {
updateTaskStatus({
groupId: Number(id),
taskListId: selectedTaskList.id,
taskId,
done,
// ...
});
};
// 할일 상태 업데이트 쿼리
export const useUpdateTaskStatus = () => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: async (params: UpdateTaskStatusRequest) =>
updateTaskStatus(params),
onSuccess: (_, params) => {
queryClient.invalidateQueries({
queryKey: tasksQueryKeys.tasks({
groupId: params.groupId,
taskListId: params.taskListId,
// ...
}),
});
// ...
},
onError: (error) => {
// ...
},
});
};
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { updateTaskStatus } from '@/apis/tasks.api';
import { tasksQueryKeys } from './keys/tasks.keys';
import { UpdateTaskStatusRequest } from '@/types/dto/requests/tasks.request.types';
export const useUpdateTaskStatus = () => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: async (params: UpdateTaskStatusRequest) =>
updateTaskStatus(params),
onMutate: async (params) => {
// 기존 쿼리 취소
await queryClient.cancelQueries({
queryKey: tasksQueryKeys.tasks({
groupId: params.groupId,
taskListId: params.taskListId,
date: formatDate(params.startDate),
}),
});
// 기존 상태
const previousTasks = queryClient.getQueryData<Task[]>(
tasksQueryKeys.tasks({
groupId: params.groupId,
taskListId: params.taskListId,
date: formatDate(params.startDate),
})
);
// 새 queryData
queryClient.setQueryData(
tasksQueryKeys.tasks({
groupId: params.groupId,
taskListId: params.taskListId,
date: formatDate(params.startDate),
}),
(old: Task[]) => {
return old.map((task: Task) =>
task.id === params.taskId ? { ...task, doneAt: params.done } : task
);
}
);
// 실패 시 사용될 이전 상태
return { previousTasks };
},
onError: (error, params, context) => {
if (context?.previousTasks) {
// 실패 시 이전 상태 적용
queryClient.setQueryData(
tasksQueryKeys.tasks({
groupId: params.groupId,
taskListId: params.taskListId,
date: formatDate(params.startDate ?? ''),
}),
context.previousTasks
);
}
},
});
};
onMutate: 이 함수는 변이 함수가 실행되기 전에 호출됩니다. 여기서 쿼리를 취소하고, 현재 쿼리 데이터를 가져와서 UI를 낙관적으로 업데이트합니다. previousTasks를 반환하여 에러 발생 시 롤백할 수 있도록 합니다.
onError: 변이 중 에러가 발생하면 onMutate에서 반환한 context를 사용하여 이전 상태로 롤백합니다.
onSettled: 변이가 성공하거나 실패한 후에 쿼리를 무효화하여 서버의 최신 데이터를 가져옵니다.
이렇게 하면 태스크의 상태가 서버 응답을 기다리지 않고 즉시 UI에 반영되며, 에러가 발생할 경우 이전 상태로 롤백됩니다.

테스트하면 UI 가 서버의 응답을 기다리지 않고 즉시 반영되는 것을 볼 수 있습니다.
React Query의 낙관적 업데이트 문서에서 더 많은 정보를 확인할 수 있습니다.