Skip to content
Go back

Mastering useFormStatus Hook in React: Reusable Submit Buttons

Mastering useFormStatus Hook in React: Build a Reusable Submit Button

The useFormStatus hook from React DOM is a game-changer for handling form submissions. It lets child components inside a form detect the form’s current status—like whether it’s pending submission—without prop drilling.

Introduced in React 19 (and available experimentally in 18.3+), it reads the closest form’s state. Perfect for loading indicators on buttons or spinners during async actions.

How useFormStatus Works

useFormStatus returns an object with these key properties:

Important: It only works inside a <form> with an action prop (server action or function).

Real-World Usecase: Reusable SubmitButton

"use client";

import { useFormStatus } from "react-dom";

interface SubmitButtonProps {
  label: string;
  loadingLabel?: string;
  className?: string;
}

export function SubmitButton({
  label,
  loadingLabel = "Processing...",
  className = "as-btn w-full text-sm font-medium",
}: SubmitButtonProps) {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      disabled={pending}
      className={`${className} ${pending ? "opacity-70 cursor-not-allowed" : ""}`}
    >
      {pending ? loadingLabel : label}
    </button>
  );
}

Key Features:

Complete Working Example

// app/signup/page.tsx
import { SubmitButton } from "@/components/SubmitButton";

export default function SignupPage() {
  async function createUser(formData: FormData) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    console.log("User created:", formData.get("email"));
  }

  return (
    <form action={createUser} className="space-y-4 p-6 max-w-md mx-auto">
      <input
        name="email"
        type="email"
        placeholder="Email"
        required
        className="w-full p-2 border"
      />
      <input
        name="password"
        type="password"
        placeholder="Password"
        required
        className="w-full p-2 border"
      />

      <SubmitButton
        label="Create Account"
        loadingLabel="Creating..."
      />
    </form>
  );
}

Pro Tips & Gotchas

// React 18 experimental setup
const formConfig = {
  experimental_useFormStatus: true
};

Why You’ll Love It

No brittle loading props. No Context providers. Components stay focused. Scales perfectly for complex forms, modals, and wizards.

Try it today—your UX (and future self) will thank you!


Share this post on:

Previous Post
Building a Birthday Discount App for Shopify POS with Modern UI Extensions
Next Post
My AI-Powered Project Planning Workflow: From Chaos to Code-Ready Architecture