Next.js 15 Fullstack (App Router + Server Actions) · درس

حالات الانتظار والتحميل باستخدام useFormStatus

عطّل الأزرار واعرض مؤشرات التحميل أثناء الإرسال باستخدام hook ‏useFormStatus داخل أبناء النموذج.

الدرس 2 من 413 خطوة

حالات الانتظار والتحميل باستخدام useFormStatus درس مجاني في Next.js 15 Fullstack (App Router + Server Actions) على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Next.js 15 Fullstack (App Router + Server Actions)، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Next.js 15 Fullstack (App Router + Server Actions) 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

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 — true while the form is submitting.
  • data — the FormData being sent.
  • method — the HTTP method (get or post).
  • action — the function or URL passed to the form's action prop.

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 pending from useFormStatus().
  • Bind it to the button's disabled attribute.
  • 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 disabled so it cannot be clicked again.
  • Use aria-disabled or 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 inside startTransition and read isPending. Works for buttons not wrapped in a form (e.g. a delete button calling an action via onClick).

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-dom and read pending (plus data, method, action).
  • Always call it inside a child rendered within the <form> — typically a "use client" SubmitButton.
  • Bind pending to disabled and 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 for useTransition instead.
البدء مجانًا

تعلم TypeScript مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
22
الدروس
88

الأسئلة الشائعة

هل درس «حالات الانتظار والتحميل باستخدام useFormStatus» مجاني؟

نعم — نص درس «حالات الانتظار والتحميل باستخدام useFormStatus» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Next.js 15 Fullstack (App Router + Server Actions)، انتقل إلى CoddyKit PRO. تتضمن دورة Next.js 15 Fullstack (App Router + Server Actions) 4 دروس في المجموع.

ماذا ستتعلم في «حالات الانتظار والتحميل باستخدام useFormStatus»؟

عطّل الأزرار واعرض مؤشرات التحميل أثناء الإرسال باستخدام hook ‏useFormStatus داخل أبناء النموذج. تتمرن على Next.js 15 Fullstack (App Router + Server Actions) مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Next.js 15 Fullstack (App Router + Server Actions)؟

لا تُشترط خبرة سابقة. Next.js 15 Fullstack (App Router + Server Actions) على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «حالات الانتظار والتحميل باستخدام useFormStatus»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Next.js 15 Fullstack (App Router + Server Actions) هذا؟

نعم. كل درس في Next.js 15 Fullstack (App Router + Server Actions) يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. التحسين التدريجي باستخدام خاصية form Action
  2. حالات الانتظار والتحميل باستخدام useFormStatus
  3. أخطاء التحقق على مستوى الحقل باستخدام useActionState
  4. ملاحظات فورية باستخدام useOptimistic
← العودة إلى Next.js 15 Fullstack (App Router + Server Actions)