المشكلة التي يحلّها Actions
في React التقليدي، أي نموذج يحتاج ثلاث حالات يدوية على الأقل: قيمة الحقل، حالة "جارٍ الإرسال" (pending)، ورسالة الخطأ. مع React 19 صار عندك نمط جاهز لهذا كلّه اسمه Actions: دالة غير متزامنة (async) تُمرَّر مباشرة إلى <form action={...}>، و React يتكفّل بإدارة الـ pending والأخطاء تلقائيًا.
خطّاف useActionState
import { useActionState } from "react";
async function submitReview(previousState, formData) {
const text = formData.get("review");
if (text.trim().length < 5) {
return { error: "التقييم قصير جدًا" };
}
await saveReview(text);
return { error: null };
}
function ReviewForm() {
const [state, formAction, isPending] = useActionState(submitReview, { error: null });
return (
<form action={formAction}>
<textarea name="review" />
{state.error && <p className="error">{state.error}</p>}
<button disabled={isPending}>
{isPending ? "جارٍ الإرسال..." : "إرسال"}
</button>
</form>
);
}
- الدالة (
submitReview) تستقبل(previousState, formData)وتُرجع الحالة الجديدة. stateهو آخر قيمة أرجعتها الدالة (تبدأ بالقيمة الابتدائية{ error: null }).formActionتُمرَّر مباشرة إلىactionالنموذج — React يغلّفها تلقائيًا داخل Transition، فلا تحتاجstartTransitionبنفسك.isPendingقيمة منطقية تصيرtrueأثناء تنفيذ الدالة.
💡 لاحظ أن
submitReviewتستقبلFormDataجاهزة — لا داعي لـuseStateمنفصل لكل حقل، ولا لـe.preventDefault()اليدوي.
قراءة حالة النموذج من مكوّن فرعي: useFormStatus
أحيانًا تريد مكوّن زر منفصل يعرف إن كان النموذج "الأب" قيد الإرسال، دون تمرير isPending كـ prop يدويًا. هنا يأتي useFormStatus من react-dom:
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "..." : "حفظ"}</button>;
}
function SettingsForm() {
return (
<form action={saveSettings}>
<input name="username" />
<SubmitButton />
</form>
);
}
useFormStatus تُرجع كائنًا فيه pending (هل النموذج قيد الإرسال؟)، وdata (بيانات النموذج المُرسَلة)، وmethod، وaction — لكن بشرط واحد صارم: يجب استدعاؤها من مكوّن يُعرَض داخل <form>، لا من نفس المكوّن الذي يرسم النموذج نفسه.
// ❌ خطأ: useFormStatus داخل نفس مكوّن <form> — pending تبقى false دائمًا
function BadForm() {
const { pending } = useFormStatus();
return <form action={save}><button disabled={pending}>حفظ</button></form>;
}
// ✅ صحيح: SubmitButton مكوّن فرعي منفصل داخل <form>
الأخطاء الشائعة
- ❌ استدعاء
useFormStatusفي نفس مكوّن<form>→ لا ترى حالة والدها، دائمًاfalse. - ❌ الاعتماد على
dispatchActionأثناء الرندر → يجب استدعاؤها من حدث (submit) لا أثناء الرسم. - ❌ نسيان أن
initialStateيُتجاهَل بعد أول استدعاء → القيمة اللاحقة تأتي من رجوع الدالة نفسها فقط.
خلاصة
useActionState يجمع state + pending + error handling في خطّاف واحد مبني حول دالة غير متزامنة تُمرَّر لـ <form action>. وuseFormStatus يتيح لأي مكوّن فرعي داخل النموذج قراءة حالته دون prop drilling. هذا هو أساس نمط Actions في React 19 — الأساس الذي يُبنى عليه الدرس التالي عن useOptimistic.