Mutations and Optimistic Updates
AdvancedA mutation changes server data and then has to make every affected view agree. Invalidation is the safe default; an optimistic update is what makes an interface feel instant.
Overview
Reads are the easy half. A write has to answer a harder question: after this succeeds, which of the things on screen are now wrong? Invalidating the affected query keys is the correct and boring answer — mark them stale, let them refetch, everything converges. For actions where a round trip is too slow to feel good, an optimistic update writes the expected result into the cache immediately and rolls back if the server disagrees. The rollback is the part people forget, and it is the part that matters.
A Mutation, and Invalidation
Change the data, then say what that invalidated.
const queryClient = useQueryClient()
const submit = useMutation({
mutationFn: (payload) => submitSolution(problemId, payload),
onSuccess: (result) => {
queryClient.invalidateQueries({ queryKey: ['submissions', problemId] })
queryClient.invalidateQueries({ queryKey: ['progress'] })
toast.success('Submitted')
},
onError: (err) => toast.error(describe(err)),
})
<button onClick={() => submit.mutate({ code })} disabled={submit.isPending}>
{submit.isPending ? 'Running…' : 'Submit'}
</button>
// isPending is the disabled state you were tracking by hand, and it
// is what stops a double-click creating two submissions.
// The discipline: after every write, list what is now stale.
// A stale sidebar counter is the classic missed invalidation.Optimistic Updates
Four callbacks: cancel, snapshot, apply, and roll back on failure.
const toggle = useMutation({
mutationFn: ({ id, done }) => setDone(id, done),
onMutate: async ({ id, done }) => {
// stop in-flight refetches from overwriting our optimistic value
await queryClient.cancelQueries({ queryKey: ['todos'] })
const previous = queryClient.getQueryData(['todos']) // snapshot
queryClient.setQueryData(['todos'], (old) =>
old.map(t => (t.id === id ? { ...t, done } : t))) // apply
return { previous } // context
},
onError: (_err, _vars, context) => {
queryClient.setQueryData(['todos'], context.previous) // roll back
toast.error('Could not save — reverted')
},
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})
// Worth it for: toggles, likes, reordering, marking as read.
// Not worth it for: payments, submissions, anything where showing a
// success that then reverses is worse than waiting.Failure Is Part of the Design
What the user sees when the write does not land, including the awkward cases.
// Retry writes carefully. A GET is safe to retry; a POST that
// creates something is not, unless the server accepts an
// idempotency key.
useMutation({ mutationFn: pay, retry: 0 })
// with a key, retrying is safe:
pay({ amount, idempotencyKey: crypto.randomUUID() })
// Keep the user's input on failure. Clearing a form after a failed
// submit is the single most annoying thing a form can do.
onError: () => { /* leave the form values exactly as they are */ }
// Warn before losing unsaved work
useEffect(() => {
if (!isDirty) return
const onLeave = (e) => { e.preventDefault(); e.returnValue = '' }
window.addEventListener('beforeunload', onLeave)
return () => window.removeEventListener('beforeunload', onLeave)
}, [isDirty])
// And map validation errors back onto fields rather than showing one
// generic banner — see the forms concepts.Key Points to Remember
- 1After a successful write, invalidate every query key whose data that write affected
- 2isPending gives you the disabled state that prevents double submission
- 3An optimistic update must cancel in-flight queries, snapshot, apply, and roll back on error
- 4Use optimistic updates for cheap reversible actions, never for payments or submissions
- 5Retrying a POST is unsafe without an idempotency key, and a failed submit must never clear the user's input
Interview Questions
Sign in to ask AriaAfter a successful mutation, how do you make the rest of the UI reflect the change?
What are the steps of a correct optimistic update?
Why is automatically retrying a failed POST risky?
Ask Aria about Mutations and Optimistic Updates
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.