React 19 Server Actions و useActionState: الدليل الشامل للتعامل مع النماذج

AI Bot
بواسطة AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

نماذج بدون تعقيد. يُغيّر React 19 جذرياً طريقة تعاملنا مع النماذج — تتيح لك Server Actions و useActionState بناء نماذج آمنة تعمل حتى قبل تحميل JavaScript.

ماذا ستتعلم

  • كيف تحلّ Server Actions محل مسارات API للتعامل مع النماذج
  • استخدام useActionState لإدارة حالة النموذج مع حالات الانتظار
  • بناء نماذج متدرجة التحسين تعمل بدون JavaScript
  • التحقق الآمن من البيانات على الخادم باستخدام Zod
  • التحديثات التفاؤلية باستخدام useOptimistic
  • أنماط عملية: نماذج متعددة الخطوات، رفع الملفات، ومعالجة الأخطاء

المتطلبات الأساسية

قبل البدء في هذا الدليل، تأكد من توفر:

  • Node.js 20+ مُثبّت على جهازك
  • معرفة أساسية بـ React و TypeScript
  • إلمام بـ Next.js App Router (الصفحات، التخطيطات، مكونات الخادم)
  • محرر أكواد مثل VS Code

لماذا تُغيّر Server Actions كل شيء

قبل React 19، كان التعامل مع النماذج يتطلب عادةً:

  1. إنشاء مسار API منفصل (/api/submit-form)
  2. استخدام useState لكل حقل في النموذج
  3. كتابة معالجات onChange لكل حقل إدخال
  4. إدارة حالات التحميل والخطأ والنجاح يدوياً
  5. استدعاء fetch() أو مكتبة لإرسال البيانات

هذا كمّ كبير من الكود المتكرر لشيء أساسي كالنماذج. تُزيل Server Actions معظم هذا التعقيد بالسماح لك بتعريف دوال خادم يستدعيها React مباشرة من العميل — بدون مسارات API، بدون استدعاءات fetch يدوية، وبدون إدارة حالة منفصلة.

// قبل: الطريقة القديمة
'use client'
import { useState } from 'react'
 
export default function ContactForm() {
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
 
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setLoading(true)
    try {
      const res = await fetch('/api/contact', {
        method: 'POST',
        body: JSON.stringify({ name, email }),
      })
      if (!res.ok) throw new Error('Failed')
    } catch (err) {
      setError('Something went wrong')
    } finally {
      setLoading(false)
    }
  }
 
  return (
    <form onSubmit={handleSubmit}>
      {/* ... الكثير من حقول الإدخال المتحكم بها */}
    </form>
  )
}
// بعد: مع Server Actions
import { submitContact } from './actions'
 
export default function ContactForm() {
  return (
    <form action={submitContact}>
      <input name="name" required />
      <input name="email" type="email" required />
      <button type="submit">إرسال</button>
    </form>
  )
}

النسخة الجديدة تعمل بدون JavaScript، ليس فيها حالة على جانب العميل، ودالة الخادم تتولى كل شيء.

الخطوة 1: إعداد المشروع

لنُنشئ مشروع Next.js 15 جديد مع React 19:

npx create-next-app@latest react19-forms --typescript --tailwind --app --src-dir
cd react19-forms

تأكد من وجود React 19 في ملف package.json:

{
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "next": "^15.0.0"
  }
}

ثبّت Zod للتحقق من البيانات على الخادم:

npm install zod

الخطوة 2: أول Server Action لك

أنشئ ملفاً لإجراءات الخادم. التوجيه "use server" في أعلى الملف يُخبر React أن هذه الوحدة تعمل على الخادم فقط:

// src/app/actions.ts
'use server'
 
export async function createUser(formData: FormData) {
  const name = formData.get('name') as string
  const email = formData.get('email') as string
 
  // هذا يعمل على الخادم — آمن للوصول لقواعد البيانات والمفاتيح السرية
  console.log('Creating user:', { name, email })
 
  // محاكاة إدخال في قاعدة البيانات
  await new Promise((resolve) => setTimeout(resolve, 1000))
 
  return { success: true, message: `User ${name} created!` }
}

الآن استخدمه في نموذج. هذا مكون خادم — لا حاجة لـ "use client":

// src/app/page.tsx
import { createUser } from './actions'
 
export default function HomePage() {
  return (
    <main className="max-w-md mx-auto mt-20 p-6">
      <h1 className="text-2xl font-bold mb-6">إنشاء حساب</h1>
      <form action={createUser} className="space-y-4">
        <div>
          <label htmlFor="name" className="block text-sm font-medium mb-1">
            الاسم
          </label>
          <input
            id="name"
            name="name"
            type="text"
            required
            className="w-full border rounded-lg px-3 py-2"
          />
        </div>
        <div>
          <label htmlFor="email" className="block text-sm font-medium mb-1">
            البريد الإلكتروني
          </label>
          <input
            id="email"
            name="email"
            type="email"
            required
            className="w-full border rounded-lg px-3 py-2"
          />
        </div>
        <button
          type="submit"
          className="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700"
        >
          إنشاء المستخدم
        </button>
      </form>
    </main>
  )
}

هذا النموذج يعمل حتى مع تعطيل JavaScript! المتصفح يُرسل النموذج بشكل طبيعي، و Next.js يتولى تنفيذ Server Action على الخادم.

الخطوة 3: إضافة useActionState للتغذية الراجعة

النموذج الأساسي يعمل، لكن لا توجد تغذية راجعة — لا حالة تحميل، لا رسائل خطأ، لا تأكيد نجاح. هنا يأتي دور useActionState.

useActionState هو خطّاف (hook) في React 19 يُدير دورة حياة إجراء النموذج:

const [state, formAction, isPending] = useActionState(action, initialState)
  • state — القيمة الحالية المُرجعة من الإجراء (أخطاء، رسائل نجاح، إلخ)
  • formAction — نسخة مُغلّفة من إجراءك لتمريرها إلى <form action={...}>
  • isPending — قيمة منطقية تُشير إلى ما إذا كان الإجراء قيد التنفيذ

أولاً، حدّث Server Action ليُرجع حالة مُهيكلة:

// src/app/actions.ts
'use server'
 
export type FormState = {
  success: boolean
  message: string
  errors?: {
    name?: string[]
    email?: string[]
  }
}
 
export async function createUser(
  prevState: FormState,
  formData: FormData
): Promise<FormState> {
  const name = formData.get('name') as string
  const email = formData.get('email') as string
 
  // التحقق الأساسي
  const errors: FormState['errors'] = {}
  if (!name || name.length < 2) {
    errors.name = ['يجب أن يكون الاسم حرفين على الأقل']
  }
  if (!email || !email.includes('@')) {
    errors.email = ['يرجى إدخال بريد إلكتروني صالح']
  }
 
  if (Object.keys(errors).length > 0) {
    return { success: false, message: 'فشل التحقق', errors }
  }
 
  // محاكاة عملية قاعدة بيانات
  await new Promise((resolve) => setTimeout(resolve, 1000))
 
  return { success: true, message: `مرحباً، ${name}!` }
}

لاحظ أن توقيع الدالة تغيّر — يُمرر useActionState الحالة السابقة كمعامل أول، و formData كمعامل ثانٍ. هذا يختلف عن Server Action العادي حيث formData هو المعامل الوحيد.

الآن أنشئ مكون العميل مع useActionState:

// src/app/create-user-form.tsx
'use client'
 
import { useActionState } from 'react'
import { createUser, type FormState } from './actions'
 
const initialState: FormState = {
  success: false,
  message: '',
}
 
export default function CreateUserForm() {
  const [state, formAction, isPending] = useActionState(createUser, initialState)
 
  return (
    <form action={formAction} className="space-y-4">
      {/* رسالة النجاح */}
      {state.success && (
        <div className="bg-green-50 border border-green-200 text-green-800 px-4 py-3 rounded-lg">
          {state.message}
        </div>
      )}
 
      {/* رسالة الخطأ العامة */}
      {!state.success && state.message && (
        <div className="bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded-lg">
          {state.message}
        </div>
      )}
 
      <div>
        <label htmlFor="name" className="block text-sm font-medium mb-1">
          الاسم
        </label>
        <input
          id="name"
          name="name"
          type="text"
          required
          className="w-full border rounded-lg px-3 py-2"
          aria-describedby="name-error"
        />
        {state.errors?.name && (
          <p id="name-error" className="text-red-600 text-sm mt-1">
            {state.errors.name[0]}
          </p>
        )}
      </div>
 
      <div>
        <label htmlFor="email" className="block text-sm font-medium mb-1">
          البريد الإلكتروني
        </label>
        <input
          id="email"
          name="email"
          type="email"
          required
          className="w-full border rounded-lg px-3 py-2"
          aria-describedby="email-error"
        />
        {state.errors?.email && (
          <p id="email-error" className="text-red-600 text-sm mt-1">
            {state.errors.email[0]}
          </p>
        )}
      </div>
 
      <button
        type="submit"
        disabled={isPending}
        className="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
      >
        {isPending ? 'جارٍ الإنشاء...' : 'إنشاء المستخدم'}
      </button>
    </form>
  )
}

حدّث صفحتك لاستخدام المكون الجديد:

// src/app/page.tsx
import CreateUserForm from './create-user-form'
 
export default function HomePage() {
  return (
    <main className="max-w-md mx-auto mt-20 p-6">
      <h1 className="text-2xl font-bold mb-6">إنشاء حساب</h1>
      <CreateUserForm />
    </main>
  )
}

الخطوة 4: التحقق الآمن مع Zod

التحقق بالنصوص الثابتة هش وسهل الكسر. لنستخدم Zod للتحقق القوي والآمن:

// src/lib/schemas.ts
import { z } from 'zod'
 
export const createUserSchema = z.object({
  name: z
    .string()
    .min(2, 'يجب أن يكون الاسم حرفين على الأقل')
    .max(50, 'يجب أن يكون الاسم أقل من 50 حرفاً'),
  email: z
    .string()
    .email('يرجى إدخال بريد إلكتروني صالح'),
  password: z
    .string()
    .min(8, 'يجب أن تكون كلمة المرور 8 أحرف على الأقل')
    .regex(/[A-Z]/, 'يجب أن تحتوي كلمة المرور على حرف كبير واحد على الأقل')
    .regex(/[0-9]/, 'يجب أن تحتوي كلمة المرور على رقم واحد على الأقل'),
})
 
export type CreateUserInput = z.infer<typeof createUserSchema>

حدّث Server Action لاستخدام Zod:

// src/app/actions.ts
'use server'
 
import { createUserSchema } from '@/lib/schemas'
 
export type FormState = {
  success: boolean
  message: string
  errors?: Record<string, string[]>
}
 
export async function createUser(
  prevState: FormState,
  formData: FormData
): Promise<FormState> {
  // التحليل والتحقق مع Zod
  const result = createUserSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    password: formData.get('password'),
  })
 
  if (!result.success) {
    return {
      success: false,
      message: 'يرجى تصحيح الأخطاء أدناه',
      errors: result.error.flatten().fieldErrors as Record<string, string[]>,
    }
  }
 
  // result.data الآن مُحدد النوع بالكامل كـ CreateUserInput
  const { name, email, password } = result.data
 
  try {
    // محاكاة إدخال في قاعدة البيانات
    await new Promise((resolve) => setTimeout(resolve, 1000))
 
    // في الإنتاج: تشفير كلمة المرور، إدخال في قاعدة البيانات
    console.log('Creating user:', { name, email })
 
    return { success: true, message: `تم إنشاء الحساب لـ ${name}!` }
  } catch (error) {
    return { success: false, message: 'فشل إنشاء الحساب. يرجى المحاولة مرة أخرى.' }
  }
}

تحقق دائماً على الخادم، حتى لو كنت تتحقق أيضاً على العميل. يمكن تجاوز التحقق على العميل — التحقق على الخادم هو حدود الأمان الحقيقية.

الخطوة 5: التحديثات التفاؤلية مع useOptimistic

للإجراءات التي تريد فيها ردود فعل فورية (مثل تبديل المفضلة أو إرسال تعليق)، يوفر React 19 خطّاف useOptimistic:

// src/app/comments/comment-form.tsx
'use client'
 
import { useActionState, useOptimistic } from 'react'
import { addComment, type CommentState } from './actions'
 
type Comment = {
  id: string
  text: string
  author: string
  pending?: boolean
}
 
export default function CommentSection({
  initialComments,
}: {
  initialComments: Comment[]
}) {
  const [optimisticComments, addOptimisticComment] = useOptimistic(
    initialComments,
    (state: Comment[], newComment: Comment) => [
      ...state,
      { ...newComment, pending: true },
    ]
  )
 
  const initialState: CommentState = { success: false, message: '' }
  const [state, formAction, isPending] = useActionState(
    async (prevState: CommentState, formData: FormData) => {
      // إضافة تعليق تفاؤلي فوراً
      addOptimisticComment({
        id: crypto.randomUUID(),
        text: formData.get('text') as string,
        author: 'أنت',
        pending: true,
      })
 
      // ثم تشغيل إجراء الخادم الفعلي
      return addComment(prevState, formData)
    },
    initialState
  )
 
  return (
    <div className="space-y-4">
      {/* قائمة التعليقات */}
      <ul className="space-y-3">
        {optimisticComments.map((comment) => (
          <li
            key={comment.id}
            className={`p-3 rounded-lg border ${
              comment.pending ? 'opacity-50 bg-gray-50' : 'bg-white'
            }`}
          >
            <p className="font-medium">{comment.author}</p>
            <p className="text-gray-600">{comment.text}</p>
            {comment.pending && (
              <span className="text-xs text-gray-400">جارٍ الإرسال...</span>
            )}
          </li>
        ))}
      </ul>
 
      {/* نموذج التعليق */}
      <form action={formAction} className="flex gap-2">
        <input
          name="text"
          placeholder="أضف تعليقاً..."
          required
          className="flex-1 border rounded-lg px-3 py-2"
        />
        <button
          type="submit"
          disabled={isPending}
          className="bg-blue-600 text-white px-4 py-2 rounded-lg disabled:opacity-50"
        >
          نشر
        </button>
      </form>
    </div>
  )
}

يظهر التعليق فوراً في واجهة المستخدم بحالة "قيد الانتظار" بصرياً، ثم يتم تأكيده (أو التراجع عنه) عند استجابة الخادم.

الخطوة 6: نمط النموذج متعدد الخطوات

للنماذج المعقدة مثل تدفقات الإعداد الأولي، يمكنك دمج Server Actions مع حالة العميل لبناء معالجات متعددة الخطوات:

// src/app/onboarding/onboarding-form.tsx
'use client'
 
import { useState } from 'react'
import { useActionState } from 'react'
import { completeOnboarding, type OnboardingState } from './actions'
 
const steps = ['الملف الشخصي', 'التفضيلات', 'المراجعة'] as const
 
export default function OnboardingForm() {
  const [currentStep, setCurrentStep] = useState(0)
 
  const initialState: OnboardingState = {
    success: false,
    message: '',
    step: 0,
  }
 
  const [state, formAction, isPending] = useActionState(
    completeOnboarding,
    initialState
  )
 
  return (
    <div className="max-w-lg mx-auto">
      {/* شريط التقدم */}
      <div className="flex mb-8">
        {steps.map((step, index) => (
          <div
            key={step}
            className={`flex-1 text-center py-2 text-sm font-medium ${
              index <= currentStep
                ? 'text-blue-600 border-b-2 border-blue-600'
                : 'text-gray-400 border-b-2 border-gray-200'
            }`}
          >
            {step}
          </div>
        ))}
      </div>
 
      <form action={formAction}>
        {/* حقل مخفي لتتبع الخطوة */}
        <input type="hidden" name="step" value={currentStep} />
 
        {/* الخطوة 1: الملف الشخصي */}
        {currentStep === 0 && (
          <div className="space-y-4">
            <input name="fullName" placeholder="الاسم الكامل" required
              className="w-full border rounded-lg px-3 py-2" />
            <input name="company" placeholder="الشركة" required
              className="w-full border rounded-lg px-3 py-2" />
          </div>
        )}
 
        {/* الخطوة 2: التفضيلات */}
        {currentStep === 1 && (
          <div className="space-y-4">
            <select name="role" className="w-full border rounded-lg px-3 py-2">
              <option value="developer">مطوّر</option>
              <option value="designer">مصمم</option>
              <option value="manager">مدير منتج</option>
            </select>
            <select name="experience" className="w-full border rounded-lg px-3 py-2">
              <option value="junior">مبتدئ (0-2 سنوات)</option>
              <option value="mid">متوسط (2-5 سنوات)</option>
              <option value="senior">متقدم (5+ سنوات)</option>
            </select>
          </div>
        )}
 
        {/* الخطوة 3: المراجعة */}
        {currentStep === 2 && (
          <div className="bg-gray-50 p-4 rounded-lg">
            <p className="text-gray-600">
              راجع معلوماتك وانقر على إرسال لإكمال الإعداد.
            </p>
          </div>
        )}
 
        {/* التنقل */}
        <div className="flex justify-between mt-6">
          <button
            type="button"
            onClick={() => setCurrentStep((s) => Math.max(0, s - 1))}
            className={`px-4 py-2 rounded-lg border ${
              currentStep === 0 ? 'invisible' : ''
            }`}
          >
            السابق
          </button>
 
          {currentStep < steps.length - 1 ? (
            <button
              type="button"
              onClick={() => setCurrentStep((s) => s + 1)}
              className="px-4 py-2 bg-blue-600 text-white rounded-lg"
            >
              التالي
            </button>
          ) : (
            <button
              type="submit"
              disabled={isPending}
              className="px-4 py-2 bg-green-600 text-white rounded-lg disabled:opacity-50"
            >
              {isPending ? 'جارٍ الإرسال...' : 'إكمال الإعداد'}
            </button>
          )}
        </div>
      </form>
    </div>
  )
}

الخطوة 7: رفع الملفات مع Server Actions

تتعامل Server Actions مع رفع الملفات بشكل طبيعي عبر FormData:

// src/app/upload/actions.ts
'use server'
 
import { writeFile } from 'fs/promises'
import path from 'path'
 
export type UploadState = {
  success: boolean
  message: string
  url?: string
}
 
export async function uploadAvatar(
  prevState: UploadState,
  formData: FormData
): Promise<UploadState> {
  const file = formData.get('avatar') as File
 
  if (!file || file.size === 0) {
    return { success: false, message: 'يرجى اختيار ملف' }
  }
 
  // التحقق من نوع الملف
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
  if (!allowedTypes.includes(file.type)) {
    return { success: false, message: 'يُسمح فقط بصور JPEG و PNG و WebP' }
  }
 
  // التحقق من حجم الملف (الحد الأقصى 5 ميغابايت)
  const maxSize = 5 * 1024 * 1024
  if (file.size > maxSize) {
    return { success: false, message: 'يجب أن يكون حجم الملف أقل من 5 ميغابايت' }
  }
 
  try {
    const bytes = await file.arrayBuffer()
    const buffer = Buffer.from(bytes)
 
    const filename = `${Date.now()}-${file.name}`
    const uploadPath = path.join(process.cwd(), 'public', 'uploads', filename)
 
    await writeFile(uploadPath, buffer)
 
    return {
      success: true,
      message: 'تم رفع الصورة بنجاح!',
      url: `/uploads/${filename}`,
    }
  } catch (error) {
    return { success: false, message: 'فشل الرفع. يرجى المحاولة مرة أخرى.' }
  }
}
// src/app/upload/avatar-form.tsx
'use client'
 
import { useActionState, useRef } from 'react'
import { uploadAvatar, type UploadState } from './actions'
 
const initialState: UploadState = { success: false, message: '' }
 
export default function AvatarUploadForm() {
  const [state, formAction, isPending] = useActionState(uploadAvatar, initialState)
  const formRef = useRef<HTMLFormElement>(null)
 
  return (
    <form ref={formRef} action={formAction} className="space-y-4">
      <div>
        <label className="block text-sm font-medium mb-2">صورة الملف الشخصي</label>
        <input
          name="avatar"
          type="file"
          accept="image/jpeg,image/png,image/webp"
          required
          className="block w-full text-sm file:mr-4 file:py-2 file:px-4
            file:rounded-lg file:border-0 file:bg-blue-50 file:text-blue-700
            hover:file:bg-blue-100"
        />
      </div>
 
      {state.message && (
        <div className={`px-4 py-3 rounded-lg text-sm ${
          state.success
            ? 'bg-green-50 text-green-800'
            : 'bg-red-50 text-red-800'
        }`}>
          {state.message}
        </div>
      )}
 
      {state.url && (
        <img
          src={state.url}
          alt="الصورة المرفوعة"
          className="w-24 h-24 rounded-full object-cover"
        />
      )}
 
      <button
        type="submit"
        disabled={isPending}
        className="bg-blue-600 text-white px-4 py-2 rounded-lg disabled:opacity-50"
      >
        {isPending ? 'جارٍ الرفع...' : 'رفع الصورة'}
      </button>
    </form>
  )
}

الخطوة 8: نمط مساعد النماذج القابل لإعادة الاستخدام

مع نمو تطبيقك، ستحتاج لنمط قابل لإعادة الاستخدام. إليك مساعداً عاماً:

// src/lib/form-utils.ts
import { z } from 'zod'
 
export type ActionState<T = undefined> = {
  success: boolean
  message: string
  errors?: Record<string, string[]>
  data?: T
}
 
export function createFormAction<TSchema extends z.ZodObject<any>, TResult = void>(
  schema: TSchema,
  handler: (data: z.infer<TSchema>) => Promise<TResult>
) {
  return async (
    prevState: ActionState<TResult>,
    formData: FormData
  ): Promise<ActionState<TResult>> => {
    const raw = Object.fromEntries(formData.entries())
    const result = schema.safeParse(raw)
 
    if (!result.success) {
      return {
        success: false,
        message: 'فشل التحقق',
        errors: result.error.flatten().fieldErrors as Record<string, string[]>,
      }
    }
 
    try {
      const data = await handler(result.data)
      return { success: true, message: 'تم بنجاح', data: data as TResult }
    } catch (error) {
      return {
        success: false,
        message: error instanceof Error ? error.message : 'حدث خطأ ما',
      }
    }
  }
}

الآن إنشاء إجراء نموذج جديد يصبح بسيطاً للغاية:

// src/app/actions.ts
'use server'
 
import { createFormAction } from '@/lib/form-utils'
import { createUserSchema } from '@/lib/schemas'
 
export const createUser = createFormAction(createUserSchema, async (data) => {
  // data مُحدد النوع بالكامل كـ { name: string, email: string, password: string }
  await db.user.create({ data })
  return { id: crypto.randomUUID() }
})

اختبار التطبيق

شغّل خادم التطوير:

npm run dev

اختبر السيناريوهات التالية:

  1. المسار الصحيح — املأ جميع الحقول بشكل صحيح وأرسل النموذج. يجب أن تظهر رسالة نجاح.
  2. أخطاء التحقق — أرسل ببريد إلكتروني غير صالح أو كلمة مرور قصيرة. يجب أن تظهر أخطاء على مستوى الحقول.
  3. JavaScript معطّل — عطّل JS في أدوات المطور وأرسل النموذج. يجب أن يعمل عبر إعادة تحميل كاملة للصفحة.
  4. حالة التحميل — انقر على إرسال ولاحظ الزر المعطّل مع نص "جارٍ الإنشاء...".
  5. أخطاء الشبكة — حاكِ فشل الخادم وتحقق من ظهور رسالة الخطأ.

استكشاف الأخطاء وإصلاحها

"Functions cannot be passed directly to Client Components"

هذا الخطأ يعني أنك تحاول تمرير Server Action كخاصية (prop) لمكون عميل بشكل غير صحيح. تأكد من:

  • أن Server Action مُعرّف في ملف 'use server'
  • أنك تستورده مباشرة في مكون العميل، أو تمرره عبر خاصية action للنموذج

"useActionState is not a function"

تأكد أنك تستخدم React 19 أو أحدث. تحقق من package.json — إذا رأيت React 18.x، قم بالترقية:

npm install react@latest react-dom@latest

بيانات النموذج فارغة

تأكد أن كل حقل إدخال يحتوي على خاصية name. يستخدم FormData خاصية name لجمع القيم — بدونها يكون الحقل غير مرئي للخادم.

النقاط الرئيسية

النمطمتى يُستخدم
action={serverAction} بسيطنماذج بسيطة في مكونات الخادم، لا حاجة لتغذية راجعة
useActionStateنماذج تحتاج حالات تحميل وأخطاء ورسائل نجاح
useOptimisticتغذية راجعة فورية للإجراءات (تعليقات، إعجابات، تبديلات)
مساعد createFormActionنمط قابل لإعادة الاستخدام للإجراءات المُتحقق منها عبر التطبيق

الخطوات التالية

  • استكشف revalidatePath و revalidateTag لتحديث البيانات المُخزنة مؤقتاً بعد التعديلات
  • ابنِ تطبيق CRUD كامل يجمع Server Actions مع Prisma أو Drizzle ORM
  • أضف تحديد معدل الطلبات (rate limiting) لإجراءات الخادم في بيئة الإنتاج
  • ادمج مع مكتبة next-safe-action لمزيد من أمان الأنواع

الخلاصة

تُمثّل Server Actions و useActionState في React 19 تبسيطاً جوهرياً في طريقة بناء النماذج. بنقل منطق التحقق والتعديل إلى الخادم، تحصل على تحسين متدرج مجاناً، وتُزيل فئات كاملة من أخطاء جانب العميل، وتكتب كوداً أقل بكثير. الأنماط في هذا الدليل — من الإجراءات الأساسية إلى التحديثات التفاؤلية إلى المساعدات القابلة لإعادة الاستخدام — تمنحك أساساً متيناً لبناء نماذج جاهزة للإنتاج في أي تطبيق Next.js.


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على تعزيز كفاءة خدمة العملاء: الاستفادة من استدعاءات الأدوات الإلزامية في ChatCompletion.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة