useFormStatus Explained: Build Better Submit Buttons

Read the pending state of a form with useFormStatus and build submit buttons that disable, label, and announce correctly while a request runs.

6 min read

useFormStatus is a React DOM Hook that reports the submission status of a parent form. A submit button rendered inside the form can read pending to disable itself, relabel while the request runs, and show the data being sent. The Hook must live in a child component, not in the component that renders the form element.

It solves a specific problem: a button deep in the form tree cannot easily know when the parent form is submitting, and useFormStatus gives it that signal without prop drilling.

Read the pending state

Call useFormStatus inside a button component, then read the pending flag to disable the button and swap its label during submission.

App.jsxApp.jsx
import { useFormStatus } from "react-dom";
 
function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Submitting..." : "Submit"}
    </button>
  );
}

The button now disables and changes its label the moment the form starts submitting. Disabling it also prevents a second click from sending the same request twice.

Put the button inside the form

The button component must be rendered inside a form element for useFormStatus to see anything. Wrap it in a form that has an action function.

App.jsxApp.jsx
export default function SignupForm() {
  async function submit(formData) {
    await fetch("/api/signup", { method: "POST", body: formData });
  }
  return (
    <form action={submit}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" />
      <SubmitButton />
    </form>
  );
}

Submit the form and the SubmitButton inside it switches to its pending label. The Hook reads the form that wraps it, so the button stays in sync with the request without any props being passed down.

Read the submitted data

The status object also carries data, the FormData the form is submitting. A component can use it to show what the user just sent.

App.jsxApp.jsx
function SubmitButton() {
  const { pending, data } = useFormStatus();
  return (
    <>
      <button type="submit" disabled={pending}>Submit</button>
      {data && <p>Sending {data.get("email")}</p>}
    </>
  );
}

While the request runs, data holds the submitted fields, so the paragraph shows the email being sent. The same pattern can confirm a username choice or echo a summary of the form.

Read method and action

Beyond pending and data, the status object exposes method and action. method is the form's HTTP method as a string, get or post, and action is a reference to the function passed to the form's action prop. Most submit buttons only need pending, but these extras help when one shared button renders inside several forms and must behave differently per form.

Keep in mind that action is null when the form has no action prop or when it uses a URL, so do not rely on it being a function.

Avoid the same-component pitfall

useFormStatus only tracks a parent form, not a form rendered in the same component that calls the Hook. Calling it in the component that returns the form element always gives pending false.

  • Extract the button into its own component and render it inside the form.
  • Pass values to the button as props when it needs more than the status.
  • Keep the action function in the parent so the child stays presentational.

This is the most common mistake with useFormStatus, and the fix is always the same: move the Hook call one level down into a child of the form.

Combine with useActionState

useFormStatus reports that a form is submitting, while useActionState adds result state and its own pending flag. You can use both: keep the state in the parent with useActionState, and read pending in a child button with useFormStatus. The button then stays presentational, and the parent owns the result.

This split is common in larger forms where the submit button and the error display live far apart in the tree.

Why a separate button component

The extraction is not just a workaround. A separate SubmitButton can be reused across every form in an app, so pending behavior stays consistent. It also keeps the parent form focused on data and validation instead of button labels.

For the full submission flow with result state, see React 19 Form Actions and useActionState explained. For the error side, see how to show server validation errors in React forms, and for the basics see how to build forms in React.

Rune AI

Rune AI

Key Insights

  • Import useFormStatus from react-dom.
  • Call it in a component rendered inside the form.
  • Read pending to disable the submit button.
  • Read data to show what is being submitted.
  • It tracks only the parent form, not sibling forms.
RunePowered by Rune AI

Frequently Asked Questions

Where do I import useFormStatus from?

From react-dom. It is a React DOM Hook, not part of the core react package.

Why is my pending value always false?

useFormStatus only reports the status of a parent form. Call it from a component rendered inside the form, not from the same component that renders the form element.

What does useFormStatus return?

A status object with pending, data, method, and action. pending is a boolean, data is the FormData being submitted, and action is the function passed to the form.

Conclusion

useFormStatus reads the parent form's submission status so a child button can disable and relabel itself. Keep the Hook inside the form, and pair it with a form action.