Skip to content

Forms & useOptimistic

You often want a submit button to know if its form is pending — to disable itself and show a spinner. Passing isPending down through every intermediate component is tedious. useFormStatus (from react-dom) lets a component read the status of the nearest parent <form> directly:

import { useFormStatus } from 'react-dom';
// This button reads the enclosing form's status — no props needed.
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Saving…' : 'Save'}
</button>
);
}
function ProfileForm({ action }) {
return (
<form action={action}>
<input name="name" />
<SubmitButton /> {/* knows the form is pending, with zero prop drilling */}
</form>
);
}

The one rule: useFormStatus reads the parent form, so SubmitButton must be rendered inside the <form>, not be the component that renders the form itself.

useOptimistic: show the result before it’s real

Section titled “useOptimistic: show the result before it’s real”

For a snappy UI, you often want to show the expected outcome immediately and correct it only if the request fails. useOptimistic gives you an optimistic value that you set instantly and that React automatically reverts to the real value once the action settles:

import { useOptimistic } from 'react';
function Thread({ messages, sendMessage }) {
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(current, newText) => [...current, { text: newText, sending: true }],
);
async function formAction(formData) {
const text = formData.get('message');
addOptimistic(text); // show it immediately, marked "sending"
await sendMessage(text); // real request; React reconciles when it resolves
}
return (
<>
{optimisticMessages.map((m, i) => (
<p key={i}>{m.text} {m.sending && <small>(sending…)</small>}</p>
))}
<form action={formAction}>
<input name="message" />
<button type="submit">Send</button>
</form>
</>
);
}

The message appears the instant you submit. When sendMessage resolves, React drops the optimistic entry in favor of the real state; if it fails, React automatically reverts, so you don’t manually roll back.

sequenceDiagram
  participant U as User
  participant O as useOptimistic
  participant S as server action
  U->>O: submit (addOptimistic)
  O->>U: show optimistic value instantly
  O->>S: await real request
  S-->>O: success → keep real state
  Note over O,U: on failure React reverts automatically
Optimistic update, then settle

Together these two hooks make a form feel instant: useOptimistic for immediate feedback, useFormStatus for the pending affordance on the button — both without threading state through props.

What does `useFormStatus` give a component?
Where must a component calling useFormStatus be rendered?
What does `useOptimistic` do when the async action fails?