Status Menunggu dan Memuat dengan useFormStatus
Nonaktifkan tombol dan tampilkan indikator pemuatan selama pengiriman menggunakan hook useFormStatus di dalam anak formulir.
Status Menunggu dan Memuat dengan useFormStatus adalah pelajaran Next.js 15 Fullstack (App Router + Server Actions) gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Next.js 15 Fullstack (App Router + Server Actions), dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Next.js 15 Fullstack (App Router + Server Actions) mencakup 4 pelajaran total.
Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.
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.
Belajar TypeScript dengan tutor AI — gratis
Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.
- Kursus
- 22
- Pelajaran
- 88
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Status Menunggu dan Memuat dengan useFormStatus” gratis?
Ya — teks lengkap “Status Menunggu dan Memuat dengan useFormStatus” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Next.js 15 Fullstack (App Router + Server Actions), upgrade ke CoddyKit PRO. Kursus Next.js 15 Fullstack (App Router + Server Actions) mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Status Menunggu dan Memuat dengan useFormStatus”?
Nonaktifkan tombol dan tampilkan indikator pemuatan selama pengiriman menggunakan hook useFormStatus di dalam anak formulir. Kamu berlatih Next.js 15 Fullstack (App Router + Server Actions) dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai Next.js 15 Fullstack (App Router + Server Actions)?
Tidak diperlukan pengalaman sebelumnya. Next.js 15 Fullstack (App Router + Server Actions) di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.
Berapa lama pelajaran “Status Menunggu dan Memuat dengan useFormStatus” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran Next.js 15 Fullstack (App Router + Server Actions) ini?
Ya. Setiap pelajaran Next.js 15 Fullstack (App Router + Server Actions) menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Peningkatan Progresif dengan Prop Action pada Formulir
- Status Menunggu dan Memuat dengan useFormStatus
- Kesalahan Validasi Tingkat Bidang dengan useActionState
- Umpan Balik Instan dengan useOptimistic