All the steps for Optimistic Updates in React Query

Just a guy who loves to write code and watch anime.
The 6-Step Optimistic Update Pattern
function useToggleTodo(id, sort) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: () => toggleTodo(id),
onMutate: async () => {
// 1. Cancel outgoing queries
await queryClient.cancelQueries({
queryKey: ['todos', 'list', { sort }]
})
// 2. Store previous state
const snapshot = queryClient.getQueryData(['todos', 'list', { sort }])
// 3. Set new optimistic state
queryClient.setQueryData(
['todos', 'list', { sort }],
(prev) => prev?.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
)
// 4. Return rollback function
return () => {
queryClient.setQueryData(['todos', 'list', { sort }], snapshot)
}
},
onError: (error, variables, rollback) => {
// 5. Rollback if error
rollback?.()
},
onSettled: () => {
// 6. Invalidate to sync with server
queryClient.invalidateQueries(['todos', 'list'])
}
})
}
Why Each Step Matters
Step 1 (Cancel): Prevents race conditions where a background refetch overwrites your optimistic update
Steps 2-4 (Snapshot/Update/Return): Immediate UI feedback + rollback capability
Step 5 (Rollback): If server says "nope", undo the optimistic change
Step 6 (Invalidate): Even on success, double-check with server to stay in sync
The Key Insight
The magic is in onMutate → it runs before the network request, so users see instant feedback. Then you handle the server response afterwards.






