useFormStatus
When a form is submitting, users need feedback exactly where they initiated the action. A global loading bar doesn't tell them which button is working. useFormStatus provides the pending state of the nearest parent form—enabling localized, contextual feedback.
The hook returns { pending, data, method, action }, but pending is what you'll use most. It becomes true when the form submits and false when the action completes.
The SubmitButton Pattern
The most common use is a reusable submit button that shows its own loading state:
'use client';
import { useFormStatus } from 'react-dom';
export function SubmitButton({ children }) {
const { pending } = useFormStatus();
return (
<Button type="submit" disabled={pending}>
{pending ? 'Saving...' : children}
</Button>
);
}Drop this into any form—no prop drilling, no context providers, no state management:
<form action={createPost}>
<Input name="title" />
<SubmitButton>Create Post</SubmitButton>
</form>The button automatically shows a spinner during submission and disables to prevent double-clicks.
The Child Component Constraint
Here's the critical detail: useFormStatus must be called from a child component of the form. It doesn't work in the same component that renders the form:
// ❌ Won't work - same component as form
function BadForm() {
const { pending } = useFormStatus(); // Always false
return <form action={action}>...</form>;
}
// ✅ Works - SubmitButton is a separate child component
function GoodForm() {
return (
<form action={action}>
<SubmitButton>Save</SubmitButton>
</form>
);
}This constraint exists because React needs to track which form the status belongs to—and that relationship is established through the component tree.
Beyond Buttons
Use the pattern for any form feedback:
function FormProgress() {
const { pending } = useFormStatus();
if (!pending) return null;
return <Progress value={undefined} className="w-full" />;
}The principle: button click → button feedback. Keep pending indicators close to where users took action.