MENU
useFormStatus
useFormStatus() is a Hook that reports the status of the nearest parent <form>'s most recent submission. Unlike the other Hooks on this page, it's imported from 'react-dom', not 'react'. It takes no arguments, and returns an object with four properties: pending (true while that form's submission is in flight), data (a FormData instance with the values being submitted, or null), method ('get' or 'post'), and action (a reference to the function passed to the form's action prop, or null).
The one rule that matters most: useFormStatus() only reports on a <form> that is an ancestor of the component calling it. Calling it in the very same component that renders the <form> — instead of in a separate child component nested inside that <form> — always returns the default idle status, since from that component's position in the tree, there is no parent form yet to report on.
Because of that rule, useFormStatus() is typically pulled out into its own small component, such as a reusable submit button, and rendered as a child wherever a <form> needs one — commonly a form whose action was created with useActionState(), covered earlier.
RESETRUNFULL
<!DOCTYPE html><html><head>
<script type="importmap">
{"imports": {"react": "https://esm.sh/react@19", "react-dom": "https://esm.sh/react-dom@19", "react-dom/client": "https://esm.sh/react-dom@19/client"}}
</script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head><body>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="react">
import { useFormStatus } from 'react-dom';
import { createRoot } from 'react-dom/client';
async function submitAction(formData) {
await new Promise((resolve) => setTimeout(resolve, 1000)); // simulate a slow submission
console.log('Submitted:', formData.get('email'));
}
function SubmitButton() {
// Correct: SubmitButton is a CHILD of <form>, so it sees the form's real status.
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Submitting...' : 'Subscribe'}</button>;
}
function App() {
// Wrong (for comparison): this is the SAME component that renders <form> below,
// not a child of it, so this 'pending' is always false.
const { pending } = useFormStatus();
return (
<div>
<p>Status read in App itself: pending = {String(pending)}</p>
<form action={submitAction}>
<input type="email" name="email" required placeholder="you@example.com" />
<SubmitButton />
</form>
</div>
);
}
createRoot(document.getElementById('root')).render(<App />);
</script>
</body></html>