Ausstehende und Ladezustände mit useFormStatus
Deaktivieren Sie Schaltflächen und zeigen Sie während des Sendens mit dem useFormStatus-Hook innerhalb von Formular-Kindern Spinner an.
Ausstehende und Ladezustände mit useFormStatus ist eine kostenlose Next.js 15 Fullstack (App Router + Server Actions)-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Next.js 15 Fullstack (App Router + Server Actions)-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Next.js 15 Fullstack (App Router + Server Actions)-Kurs umfasst insgesamt 4 Lektionen.
Teile dieser Lektion wurden noch nicht übersetzt und werden auf Englisch angezeigt.
Why Pending States Matter
When a user submits a form backed by a Server Action, there is a network round-trip while the action runs on the server. Without feedback the user may click Submit twice, creating duplicate records.
- Disable the submit button while the action is in flight.
- Show a spinner or
Saving...label so the UI feels responsive. - Prevent double submissions automatically.
Next.js 15 (built on React 19) gives us a dedicated hook for exactly this: useFormStatus.
Meet useFormStatus
useFormStatus is a React hook imported from react-dom. It reports the status of the nearest parent <form> element.
It returns an object with these fields:
pending—truewhile the form is submitting.data— theFormDatabeing sent.method— the HTTP method (getorpost).action— the function or URL passed to the form'sactionprop.
For loading UX, pending is the field you will reach for most.
import { useFormStatus } from "react-dom";
// Returns: { pending, data, method, action }
const { pending } = useFormStatus();The Golden Rule: Call It Inside a Child
The most important rule: useFormStatus must be called from a component rendered inside the <form>, not from the component that renders the <form> itself.
It reads the status of its parent form, so if you call it in the same component that contains the <form> tag, it has no parent form to track and pending stays false forever.
The standard pattern is to extract a small SubmitButton client component and place it between the form tags.
A Basic SubmitButton Component
Create a dedicated client component for the submit button. Because it uses a hook, it needs the "use client" directive.
- Read
pendingfromuseFormStatus(). - Bind it to the button's
disabledattribute. - Swap the label based on
pending.
"use client";
import { useFormStatus } from "react-dom";
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Saving..." : "Save"}
</button>
);
}Wiring It Into a Form
Now render the SubmitButton inside a form whose action is a Server Action. The button automatically knows about the parent form's status — no props need to be passed down.
The parent form can stay a Server Component; only the button is a client component.
import { SubmitButton } from "./submit-button";
import { createPost } from "./actions";
export default function NewPostForm() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="body" required />
<SubmitButton />
</form>
);
}The Server Action Side
The pending flag becomes true the moment the form is submitted and flips back to false when the Server Action resolves. Here is a typical action that takes time (DB write + revalidation).
Mark the file with "use server" so each exported function is callable as a Server Action.
"use server";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";
export async function createPost(formData: FormData) {
const title = String(formData.get("title"));
const body = String(formData.get("body"));
await db.post.create({ data: { title, body } });
revalidatePath("/posts");
}Adding a Spinner
Beyond a label swap, you often want a visual spinner. Conditionally render a spinner element when pending is true and keep the button disabled.
- Keep the button
disabledso it cannot be clicked again. - Use
aria-disabledor visually hidden text for accessibility.
"use client";
import { useFormStatus } from "react-dom";
import { Spinner } from "@/components/spinner";
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending} aria-disabled={pending}>
{pending && <Spinner />}
{pending ? "Submitting" : "Submit"}
</button>
);
}Disabling Other Fields Too
useFormStatus isn't limited to buttons. Any child component inside the form can read pending and react to it. A common pattern is disabling inputs while submitting so the user can't edit data mid-flight.
Just remember each such component must be a client component placed inside the form.
"use client";
import { useFormStatus } from "react-dom";
export function TitleField() {
const { pending } = useFormStatus();
return (
<input
name="title"
required
disabled={pending}
placeholder="Post title"
/>
);
}Inspecting the Submitted Data
The data field exposes the in-flight FormData. This lets you show an optimistic preview of what is being saved — for example echoing the title while the request runs.
Guard for null: data is only populated while pending is true.
"use client";
import { useFormStatus } from "react-dom";
export function PendingPreview() {
const { pending, data } = useFormStatus();
if (!pending) return null;
const title = data?.get("title")?.toString() ?? "";
return <p>Saving \u201c{title}\u201d...</p>;
}useFormStatus vs useTransition
Two hooks track pending UI, but they solve different problems:
useFormStatus— purpose-built for forms; reads the nearest parent<form>'s status. No state to manage, but the component must live inside the form.useTransition— general-purpose; you call the action insidestartTransitionand readisPending. Works for buttons not wrapped in a form (e.g. a delete button calling an action viaonClick).
For declarative <form action={...}> submissions, prefer useFormStatus.
Pure TypeScript: Modeling the Status
Here is a framework-free way to think about what useFormStatus returns. We model the status shape and a tiny state machine that mirrors how pending flips during a submission.
This runs in any TypeScript judge — no React or server needed.
type FormStatus = {
pending: boolean;
method: "get" | "post" | null;
};
function simulateSubmit(): FormStatus[] {
const timeline: FormStatus[] = [];
timeline.push({ pending: false, method: null }); // idle
timeline.push({ pending: true, method: "post" }); // submitting
timeline.push({ pending: false, method: null }); // resolved
return timeline;
}
for (const s of simulateSubmit()) {
console.log(`pending=${s.pending} method=${s.method}`);
}Quick Check
You add useFormStatus() directly inside the same component that renders the <form> tag and bind pending to the submit button. The button never disables. Why?
Recap
You learned how to give forms responsive feedback with useFormStatus:
- Import it from
react-domand readpending(plusdata,method,action). - Always call it inside a child rendered within the
<form>— typically a"use client"SubmitButton. - Bind
pendingtodisabledand swap labels or show a spinner to prevent double submissions. - The parent form can stay a Server Component; only the interactive child is a client component.
- For actions not wrapped in a
<form>, reach foruseTransitioninstead.
Häufig gestellte Fragen
Ist die Lektion „Ausstehende und Ladezustände mit useFormStatus“ kostenlos?
Ja — der vollständige Text von „Ausstehende und Ladezustände mit useFormStatus“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Next.js 15 Fullstack (App Router + Server Actions)-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Next.js 15 Fullstack (App Router + Server Actions)-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Ausstehende und Ladezustände mit useFormStatus“?
Deaktivieren Sie Schaltflächen und zeigen Sie während des Sendens mit dem useFormStatus-Hook innerhalb von Formular-Kindern Spinner an. Du übst Next.js 15 Fullstack (App Router + Server Actions) mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Next.js 15 Fullstack (App Router + Server Actions) zu starten?
Keine Vorkenntnisse erforderlich. Next.js 15 Fullstack (App Router + Server Actions) auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.
Wie lange dauert die Lektion „Ausstehende und Ladezustände mit useFormStatus“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Next.js 15 Fullstack (App Router + Server Actions)-Lektion Code schreiben und ausführen?
Ja. Jede Next.js 15 Fullstack (App Router + Server Actions)-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Progressive Enhancement mit der form-Action-Prop
- Ausstehende und Ladezustände mit useFormStatus
- Feldbezogene Validierungsfehler mit useActionState
- Sofortiges Feedback mit useOptimistic