Skip to main content

suspense

Description: Defines if the useMutation hook should use React Suspense mode.

Deprecated

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):

  1. User clicks the buttonmutate() is called.
  2. useMutation initiates the action → A Promise is created.
  3. useMutation throws the Promise → This is the key behavior!
  4. React Suspense catches the thrown Promise → Stops rendering AddTodo.
  5. <LoadingSpinner /> is displayed → Fallback shows while waiting for the mutation.
  6. Mutation completes → Promise resolves with data or an error.
  7. React re-renders AddTodo → Now the mutation result is available in the cache.
  8. Component displays the result → No more Promise thrown.

What happens step by step with suspense: false:

  1. User clicks the buttonmutate() is called.
  2. useMutation initiates the action → A Promise is created.
  3. useMutation returns { isLoading: true, ... } → No Promise thrown.
  4. Component continues rendering → Must handle isLoading manually.
  5. You must check isLoading → Show your own loading UI (e.g., inside the button).
  6. Mutation completesisLoading becomes false, result is set.
  7. 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.