suspense
Description: Defines if the useMutation hook should use React Suspense mode.
suspense on useMutation is deprecated and kept only for backwards compatibility. Suspending on a mutation throws the mutation promise during render, which unmounts the form and loses its local state — Suspense is designed for reads, not writes.
Do this instead: drive writes with a React transition or a <form action={mutate}>, read isPending for the pending UI, and revalidate with client.invalidate(key). See Transitions & Form Actions and the best practices guide.
// Recommended: a transition, not suspense
const { mutate, isPending } = useMutation();
const client = useDataClient();
const [isSaving, startTransition] = useTransition();
const save = () =>
startTransition(async () => {
await mutate(() => api.saveProfile(data), { throwOnError: true });
await client.invalidate(['profile', data.id]);
});
The rest of this page documents the legacy behavior for existing code.
Deep Dive: How suspense works step by step
Example:
function AddTodo() {
const { mutate } = useMutation({
suspense: true // Default
});
return <button onClick={() => mutate(addTodoAction)}>Add Todo</button>;
}
<Suspense fallback={<LoadingSpinner />}>
<AddTodo />
</Suspense>
What happens step by step with suspense: true (default):
- User clicks the button →
mutate()is called. useMutationinitiates the action → A Promise is created.useMutationthrows the Promise → This is the key behavior!- React Suspense catches the thrown Promise → Stops rendering
AddTodo. <LoadingSpinner />is displayed → Fallback shows while waiting for the mutation.- Mutation completes → Promise resolves with data or an error.
- React re-renders
AddTodo→ Now the mutation result is available in the cache. - Component displays the result → No more Promise thrown.
What happens step by step with suspense: false:
- User clicks the button →
mutate()is called. useMutationinitiates the action → A Promise is created.useMutationreturns{ isLoading: true, ... }→ No Promise thrown.- Component continues rendering → Must handle
isLoadingmanually. - You must check
isLoading→ Show your own loading UI (e.g., inside the button). - Mutation completes →
isLoadingbecomesfalse, result is set. - Component re-renders → Now displays actual result or error.
Key difference: With suspense: true, the Promise is thrown, causing React Suspense to take over the UI and show a global or nested fallback. With suspense: false, you manage the loading state locally within the component.