Facebook Pixel

Nested Forms with useFormStatus

In this tutorial we will learn how nested forms work in Next.js and how useFormStatus helps you manage loading states inside deeply nested components. This pattern is essential when building real-world forms where submit buttons, inputs, and status messages live in different parts of the UI.

What Are Nested Forms?

A nested form is simply a form where:

  • inputs
  • submit buttons
  • messages

…are located in different parts of the component tree.

Example structure:

Form
 ├── InputSection
 ├── SettingsSection
 └── Footer
       └── SubmitButton

The submit button is not directly next to the form tag. It may be several components deep.

React 19 makes this easy by allowing nested components to access the form’s submission state using useFormStatus.

Why Nested Forms Matter

Many real apps need this:

  • A form at the top of the page
  • Inputs inside tabs or panels
  • Submit button inside a footer bar
  • Loading spinner inside a side component
  • A “Save” button inside a toolbar

Without nested form support, component structure becomes messy.

Next.js and React 19 solve this cleanly.

Step 1: Create a Server Action

First create an Action the form will use.

app/actions/updateProfile.js
"use server";
 
export async function updateProfile(previousState, formData) {
  const name = formData.get("name");
  const bio = formData.get("bio");
 
  await new Promise(r => setTimeout(r, 1200)); // simulate slow network
 
  return { saved: true, name, bio };
}

This Action returns updated data that useActionState will store.

Step 2: Create the Form Component

app/page.js
"use client";
 
import { useActionState } from "react";
import { updateProfile } from "./actions/updateProfile";
import ProfileFields from "./components/ProfileFields";
import FormFooter from "./components/FormFooter";
 
export default function ProfileForm() {
  const [state, action] = useActionState(updateProfile, {
    saved: false,
    name: "",
    bio: ""
  });
 
  return (
    <div className="p-8">
      <h1 className="text-xl font-bold mb-6">Nested Forms with useFormStatus</h1>
 
      <form action={action} className="space-y-6 p-6 border rounded shadow-md max-w-lg">
        {/* These components are nested inside the form */}
        <ProfileFields />
 
        <FormFooter />
 
        {state.saved && <p className="text-green-600 font-medium">Profile saved!</p>}
      </form>
    </div>
  );
}

What’s important:

  • <form action={action}> handles submission
  • Nested components can read form status
  • The form’s result updates via useActionState
  • page.js is a Client Component ("use client") because it uses useActionState.

Step 3: Create a Nested Input Component

app/components/ProfileFields.js
export default function ProfileFields() {
  return (
    <div className="space-y-3">
      <h3 className="font-semibold">User Details</h3>
      <input
        name="name"
        placeholder="Name"
        className="border p-2 rounded w-full text-black"
      />
 
      <textarea
        name="bio"
        placeholder="Bio"
        className="border p-2 rounded w-full text-black"
      />
    </div>
  );
}

These inputs live at a different level in the tree.

Step 4: Create a Deeply Nested Submit Button Using useFormStatus

app/components/FormFooter.js
import SubmitButton from "./SubmitButton";
 
export default function FormFooter() {
  return (
    <div className="flex justify-end pt-4 border-t">
      <SubmitButton />
    </div>
  );
}

Now create the submit button:

app/components/SubmitButton.js
"use client";
 
import { useFormStatus } from "react-dom";
 
export default function SubmitButton() {
  const { pending } = useFormStatus();
 
  return (
    <button
      disabled={pending}
      className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
    >
      {pending ? "Saving..." : "Save Profile"}
    </button>
  );
}

Key Idea

useFormStatus() works inside any descendant of the form, even several levels deep.

It gives you: pending, whether the form is currently being submitted

Important Note: To use useFormStatus, the component must be rendered inside a form. Also, because it uses a hook, SubmitButton must be a Client Component ("use client").

We did not manually pass props. React automatically provides the submission status to nested components.

How Nested Forms Work Behind the Scenes

When a form submits:

  1. React starts an Action Transition
  2. useFormStatus() becomes pending: true in all nested components
  3. The Action runs on the server
  4. When the Action finishes, useActionState updates the state
  5. pending becomes false
  6. The UI re-renders smoothly

This removes the need for:

  • Prop drilling
  • Custom loading state
  • Manual synchronization
  • Lifting the state up

React handles all coordination between Action and UI.

Multiple Submit Buttons in Different Locations

You can have multiple submit buttons inside the form.

Example:

<FormHeaderSubmit />
<FormFooterSubmit />

Each one uses:

const { pending } = useFormStatus();

All of them will correctly:

  • Disable during submission
  • Show loading states
  • Reflect the same Action transition

Next.js makes this pattern effortless.

Nested forms with useFormStatus make Next.js incredibly powerful and beginner-friendly. You can place your inputs, buttons, and messages anywhere in your UI tree, and React still knows which form they belong to. Combined with Actions and useActionState, this gives you a clean, declarative way to build real-world forms without the headaches of manual event handlers or state wiring.