Capítulo 30 de 80
Two independent ways to show a mutation's result before the server confirms it: the simpler "via the UI" approach renders mutation.variables directly while isPending, needing no rollback logic; the "via the cache" approach writes the optimistic value into setQueryData from onMutate and rolls it back in onError — needed when more than one place on screen must reflect the pending change.
variables (and isPending/isError) straight off the mutation result and render an extra, visually-distinct (e.g. dimmed) item for the in-flight mutation; it disappears automatically once the invalidation-driven refetch (from onSettled) lands. On error, variables stays available (not cleared), so you can keep showing the failed item with a retry button.useMutationState({ filters: { mutationKey, status: 'pending' }, select }) reads pending mutations' variables from anywhere, keyed by a shared mutationKey. The result is always an array (multiple concurrent mutations possible) — mutation.state.submittedAt makes a good unique key per item.onMutate should (1) cancelQueries for the affected key so an in-flight background refetch can't stomp the optimistic write, (2) snapshot the current cache value with getQueryData, (3) write the optimistic value with setQueryData, and (4) return the snapshot so onError can restore it exactly (context.client.setQueryData(key, onMutateResult.previousTodos)). onSettled still invalidates afterward regardless of outcome, to reconcile with the server's real state.// Via the UI — no rollback needed
const { isPending, variables, mutate } = useMutation({
mutationFn: (newTodo) => axios.post('/api/data', { text: newTodo }),
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})
// render: {isPending && <li style={{ opacity: 0.5 }}>{variables}</li>}
// Via the cache — full rollback pattern
useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo, context) => {
await context.client.cancelQueries({ queryKey: ['todos'] })
const previousTodos = context.client.getQueryData(['todos'])
context.client.setQueryData(['todos'], (old) => [...old, newTodo])
return { previousTodos }
},
onError: (err, newTodo, onMutateResult, context) => {
context.client.setQueryData(['todos'], onMutateResult.previousTodos)
},
onSettled: (data, error, variables, onMutateResult, context) =>
context.client.invalidateQueries({ queryKey: ['todos'] }),
})
variables + isPending) — it's less code and needs no rollback logic, and covers the common single-location case.cancelQueries before an optimistic setQueryData write in onMutate — otherwise a concurrent background refetch can silently overwrite your optimistic value before the mutation even resolves.cancelQueries, required before the cache-based optimistic write.onSettled's reconciliation step in both strategies.