useFormStatus is a React hook that reports whether the form it sits inside is submitting. In a Next.js form, it is how a submit button shows a pending state while a Server Action runs on the server.
The hook must live in a component that is a child of the form, not in the component that renders the form itself. A submit button is the usual place.
The hook does not send or mutate anything itself. It only reports the submission state of the form that wraps it, so all the actual work stays in the Server Action.
// app/submit-button.tsx
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>{pending ? 'Submitting...' : 'Submit'}</button>
}When the form submits, pending flips to true and the button disables itself with a new label. When the action finishes, pending returns to false. A button inside a form defaults to type submit, so it does not need the attribute spelled out.
// app/page.tsx
import { SubmitButton } from './submit-button'
import { subscribe } from './actions'
export default function Page() {
return <form action={subscribe}>
<label>Email <input name="email" required /></label>
<SubmitButton />
</form>
}The button is a child of the form, so the hook sees the submission. It is marked as a Client Component because hooks only run in the browser, while the page around it stays a Server Component.
The returned status object
The hook returns more than pending. It also gives data, the FormData being submitted, plus method and action. Most forms only read pending, but data is handy for a live preview of the value being sent.
For example, while a search form submits, you can render the typed query from data.get('query') so the visitor sees what they sent even after the input is cleared or disabled.
Each field is null or false when there is no active submission. data is null, method defaults to the string get, and action is null whenever the parent form has a URL action or none at all. The method and action fields are rarely needed in ordinary forms, so most components only destructure pending.
Why the child component matters
The hook only tracks the parent form. Calling it in the same component that renders the form always returns pending false, because from that component's point of view there is no parent form.
This is the most common mistake with the hook. If a button never disables at all, check that the hook call is inside the form, not beside it.
Moving the hook into a small child component is the standard fix, which is why the pattern always pairs useFormStatus with a presentational submit button. The same rule applies to any other child that wants the status, such as a spinner or a disabled fieldset.
Success state
pending covers the loading phase, but a success message usually needs the action's return value. Pair useFormStatus with useActionState, which reads the returned data, and show the result once pending goes back to false.
The split is worth remembering. useFormStatus answers "is it submitting", and useActionState answers "what came back", so a form that needs both reads one hook in the button and the other in the form component.
A common pattern is a confirmation line that appears only when the action returned without errors. Render it as text rather than a color change, and keep the error and success messages separate so the visitor always knows which one happened after the button re-enables.
See displaying field-level validation errors from a Server Action for the error half, and building forms in Next.js with Server Actions for the base pattern.
Rune AI
Key Insights
- Import useFormStatus from react-dom.
- Call it in a component inside the form, not in the form itself.
- Disable the submit button while pending is true.
- Use useActionState to read a returned success value.
- Mark the child component with use client because hooks run in the browser.
Frequently Asked Questions
Where can I call useFormStatus?
What does useFormStatus return?
Does useFormStatus show a success state?
Conclusion
useFormStatus reports whether the form it sits inside is submitting. Put it in a child submit button to disable the button during a Server Action, and pair it with useActionState when you also need the action's result.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.