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.jsis a Client Component ("use client") because it usesuseActionState.
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:
- React starts an Action Transition
useFormStatus()becomespending: truein all nested components- The Action runs on the server
- When the Action finishes,
useActionStateupdates the state pendingbecomesfalse- 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.