C.W.K.
Stream
Lesson 03 of 06 · published

useFormStatus — Pending Without Prop-Drilling

~11 min · useformstatus, react-19, actions

Level 0React Novice
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
The submit button needs to know if the form is submitting. Threading isPending down from the parent works, but it's noise. useFormStatus reads it from the enclosing form directly.

The shape

const { pending, data, method, action } = useFormStatus(). Inside a component rendered inside a <form action={fn}>, the hook reports the form's current submission state. The most-used field is pending.

The boundary rule

useFormStatus reads the nearest enclosing form. It works only in components rendered as descendants of that form. Calling it in a sibling or parent returns the default (pending: false).

The composition win

Before useFormStatus, a custom <SubmitButton> needed a prop: <SubmitButton isPending={isPending}>. With useFormStatus, the button reads the status itself. The parent renders <SubmitButton /> and the button stays in sync with whichever form it lands inside.

The data field

While a submit is in flight, data contains the submitted FormData. You can use it to show an optimistic preview of what's being submitted ('Saving title: My New Title...'). Pairs naturally with useOptimistic (next lesson).

Co-locate the pending UI with the thing that's pending. The submit button knows about its own pending state; the input knows about its own disabled state. No central isPending state, no prop-drilling. Each piece reads from the form context directly.

Code

A reusable SubmitButton that reads its own status·tsx
import { useFormStatus } from "react-dom";

export function SubmitButton({ children, className = "" }: { children: React.ReactNode; className?: string }) {
  const { pending } = useFormStatus();
  return (
    <button
      type="submit"
      disabled={pending}
      className={`px-4 py-2 bg-brand text-bg rounded disabled:opacity-50 ${className}`}
    >
      {pending ? "Saving…" : children}
    </button>
  );
}

// Use inside any form — no props needed.
// <form action={saveDraft}><input name="title" /><SubmitButton>Save</SubmitButton></form>
An optimistic preview of the submitted data·tsx
import { useFormStatus } from "react-dom";

function PendingPreview() {
  const { pending, data } = useFormStatus();
  if (!pending || !data) return null;
  const title = data.get("title") as string;
  return (
    <p className="text-muted text-sm italic">
      Saving title: {title}…
    </p>
  );
}

// Drop <PendingPreview /> anywhere inside the form — it lights up
// only while a submit is in flight and shows what was submitted.

External links

Exercise

Refactor your subscribe-form from lesson 2 to use a <SubmitButton> that reads its own pending state via useFormStatus. Remove the disabled={isPending} prop you were threading. Confirm the button still disables during submit. Then add a <PendingPreview> that shows the email being submitted (with useFormStatus's data field) and verify it lights up only during in-flight submission.
Hint
Move the SubmitButton import to its own component file (components/SubmitButton.tsx) — the win is that it's now reusable across every form in the app without props.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.