النماذج والتحولات (Forms and Mutations)

تمكنك النماذج من إنشاء وتحديث البيانات في تطبيقات الويب. يوفر Next.js طريقة قوية للتعامل مع إرسال النماذج وتحولات البيانات باستخدام إجراءات الخادم (Server Actions).

أمثلة

كيفية عمل إجراءات الخادم

مع إجراءات الخادم، لا تحتاج إلى إنشاء نقاط نهاية API يدويًا. بدلاً من ذلك، يمكنك تعريف دوال غير متزامنة يمكن استدعاؤها مباشرة من مكوناتك.

🎥 شاهد: تعلم المزيد عن النماذج والتحولات مع موجه التطبيق → YouTube (10 دقائق).

يمكن تعريف إجراءات الخادم في مكونات الخادم أو استدعاؤها من مكونات العميل. تعريف الإجراء في مكون الخادم يسمح للنموذج بالعمل بدون JavaScript، مما يوفر تحسينًا تدريجيًا.

قم بتمكين إجراءات الخادم في ملف next.config.js:

next.config.js
module.exports = {
  experimental: {
    serverActions: true,
  },
}

معلومة مفيدة:

  • يمكن للنماذج التي تستدعي إجراءات الخادم من مكونات الخادم أن تعمل بدون JavaScript.
  • النماذج التي تستدعي إجراءات الخادم من مكونات العميل ستصطف عمليات الإرسال إذا لم يتم تحميل JavaScript بعد، مع إعطاء الأولوية لترطيب العميل.
  • ترث إجراءات الخادم وقت التشغيل (runtime) من الصفحة أو التخطيط الذي تستخدمه.
  • تعمل إجراءات الخادم مع المسارات الثابتة بالكامل (بما في ذلك إعادة التحقق من البيانات مع ISR).

إعادة التحقق من البيانات المخزنة مؤقتًا

تتكامل إجراءات الخادم بعمق مع بنية التخزين المؤقت وإعادة التحقق (caching and revalidation) في Next.js. عند إرسال نموذج، يمكن لإجراء الخادم تحديث البيانات المخزنة مؤقتًا وإعادة التحقق من أي مفاتيح تخزين مؤقت يجب تغييرها.

بدلاً من أن تكون محدودة بنموذج واحد لكل مسار كما في التطبيقات التقليدية، تمكنك إجراءات الخادم من وجود إجراءات متعددة لكل مسار. علاوة على ذلك، لا يحتاج المتصفح إلى التحديث عند إرسال النموذج. في رحلة شبكة واحدة، يمكن لـ Next.js إرجاع كل من واجهة المستخدم المحدثة والبيانات المنعشة.

عرض الأمثلة أدناه لـ إعادة التحقق من البيانات من إجراءات الخادم.

أمثلة

نماذج الخادم فقط

لإنشاء نموذج خادم فقط، قم بتعريف إجراء الخادم في مكون الخادم. يمكن تعريف الإجراء إما مضمنًا مع التوجيه "use server" في أعلى الدالة، أو في ملف منفصل مع التوجيه في أعلى الملف.

export default function Page() {
  async function create(formData: FormData) {
    'use server'

    // تحويل البيانات
    // إعادة التحقق من التخزين المؤقت
  }

  return <form action={create}>...</form>
}
export default function Page() {
  async function create(formData) {
    'use server'

    // تحويل البيانات
    // إعادة التحقق من التخزين المؤقت
  }

  return <form action={create}>...</form>
}

معلومة مفيدة: يأخذ <form action={create}> نوع البيانات FormData. في المثال أعلاه، يمكن الوصول إلى FormData المرسل عبر form HTML في إجراء الخادم create.

إعادة التحقق من البيانات

تسمح لك إجراءات الخادم بإبطال تخزين Next.js المؤقت (Next.js Cache) عند الطلب. يمكنك إبطال جزء مسار كامل باستخدام revalidatePath:

'use server'

import { revalidatePath } from 'next/cache'

export default async function submit() {
  await submitForm()
  revalidatePath('/')
}
'use server'

import { revalidatePath } from 'next/cache'

export default async function submit() {
  await submitForm()
  revalidatePath('/')
}

أو إبطال جلب بيانات محدد باستخدام علامة تخزين مؤقت مع revalidateTag:

'use server'

import { revalidateTag } from 'next/cache'

export default async function submit() {
  await addPost()
  revalidateTag('posts')
}
'use server'

import { revalidateTag } from 'next/cache'

export default async function submit() {
  await addPost()
  revalidateTag('posts')
}

إعادة التوجيه

إذا كنت ترغب في إعادة توجيه المستخدم إلى مسار مختلف بعد اكتمال إجراء الخادم، يمكنك استخدام redirect وأي عنوان URL مطلق أو نسبي:

'use server'

import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'

export default async function submit() {
  const id = await addPost()
  revalidateTag('posts') // تحديث المشاركات المخزنة مؤقتًا
  redirect(`/post/${id}`) // الانتقال إلى المسار الجديد
}
'use server'

import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'

export default async function submit() {
  const id = await addPost()
  revalidateTag('posts') // تحديث المشاركات المخزنة مؤقتًا
  redirect(`/post/${id}`) // الانتقال إلى المسار الجديد
}

التحقق من صحة النموذج

نوصي باستخدام التحقق من صحة HTML مثل required و type="email" للتحقق الأساسي من صحة النموذج.

للتحقق من صحة الخادم الأكثر تقدمًا، استخدم مكتبة تحقق من المخططات مثل zod للتحقق من هيكل بيانات النموذج المحللة:

import { z } from 'zod'

const schema = z.object({
  // ...
})

export default async function submit(formData: FormData) {
  const parsed = schema.parse({
    id: formData.get('id'),
  })
  // ...
}
import { z } from 'zod'

const schema = z.object({
  // ...
})

export default async function submit(formData) {
  const parsed = schema.parse({
    id: formData.get('id'),
  })
  // ...
}

عرض حالة التحميل

استخدم خطاف useFormStatus لعرض حالة التحميل عند إرسال نموذج على الخادم. يمكن استخدام خطاف useFormStatus فقط كعنصر فرعي لعنصر form يستخدم إجراء خادم.

على سبيل المثال، زر الإرسال التالي:

'use client'

import { experimental_useFormStatus as useFormStatus } from 'react-dom'

export function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" aria-disabled={pending}>
      إضافة
    </button>
  )
}
'use client'

import { experimental_useFormStatus as useFormStatus } from 'react-dom'

export function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" aria-disabled={pending}>
      إضافة
    </button>
  )
}

يمكن بعد ذلك استخدام <SubmitButton /> في نموذج مع إجراء خادم:

import { SubmitButton } from '@/app/submit-button'

export default async function Home() {
  return (
    <form action={...}>
      <input type="text" name="field-name" />
      <SubmitButton />
    </form>
  )
}
import { SubmitButton } from '@/app/submit-button'

export default async function Home() {
  return (
    <form action={...}>
      <input type="text" name="field-name" />
      <SubmitButton />
    </form>
  )
}

معالجة الأخطاء

يمكن لإجراءات الخادم (Server Actions) أيضًا إرجاع كائنات قابلة للتسلسلة. على سبيل المثال، قد يعالج إجراء الخادم الخاص بك الأخطاء الناتجة عن إنشاء عنصر جديد:

'use server'

export async function createTodo(prevState: any, formData: FormData) {
  try {
    await createItem(formData.get('todo'))
    return revalidatePath('/')
  } catch (e) {
    return { message: 'Failed to create' }
  }
}
'use server'

export async function createTodo(prevState, formData) {
  try {
    await createItem(formData.get('todo'))
    return revalidatePath('/')
  } catch (e) {
    return { message: 'Failed to create' }
  }
}

ثم، من مكون العميل (Client Component)، يمكنك قراءة هذه القيمة وعرض رسالة خطأ.

'use client'

import { experimental_useFormState as useFormState } from 'react-dom'
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
import { createTodo } from '@/app/actions'

const initialState = {
  message: null,
}

function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" aria-disabled={pending}>
      Add
    </button>
  )
}

export function AddForm() {
  const [state, formAction] = useFormState(createTodo, initialState)

  return (
    <form action={formAction}>
      <label htmlFor="todo">Enter Task</label>
      <input type="text" id="todo" name="todo" required />
      <SubmitButton />
      <p aria-live="polite" className="sr-only">
        {state?.message}
      </p>
    </form>
  )
}
'use client'

import { experimental_useFormState as useFormState } from 'react-dom'
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
import { createTodo } from '@/app/actions'

const initialState = {
  message: null,
}

function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" aria-disabled={pending}>
      Add
    </button>
  )
}

export function AddForm() {
  const [state, formAction] = useFormState(createTodo, initialState)

  return (
    <form action={formAction}>
      <label htmlFor="todo">Enter Task</label>
      <input type="text" id="todo" name="todo" required />
      <SubmitButton />
      <p aria-live="polite" className="sr-only">
        {state?.message}
      </p>
    </form>
  )
}

التحديثات التفاؤلية (Optimistic Updates)

استخدم useOptimistic لتحديث واجهة المستخدم بشكل تفاؤلي قبل انتهاء إجراء الخادم، بدلاً من انتظار الرد:

'use client'

import { experimental_useOptimistic as useOptimistic } from 'react'
import { send } from './actions'

type Message = {
  message: string
}

export function Thread({ messages }: { messages: Message[] }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[]>(
    messages,
    (state: Message[], newMessage: string) => [
      ...state,
      { message: newMessage },
    ]
  )

  return (
    <div>
      {optimisticMessages.map((m, k) => (
        <div key={k}>{m.message}</div>
      ))}
      <form
        action={async (formData: FormData) => {
          const message = formData.get('message')
          addOptimisticMessage(message)
          await send(message)
        }}
      >
        <input type="text" name="message" />
        <button type="submit">Send</button>
      </form>
    </div>
  )
}
'use client'

import { experimental_useOptimistic as useOptimistic } from 'react'
import { send } from './actions'

export function Thread({ messages }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [...state, { message: newMessage }]
  )

  return (
    <div>
      {optimisticMessages.map((m) => (
        <div>{m.message}</div>
      ))}
      <form
        action={async (formData) => {
          const message = formData.get('message')
          addOptimisticMessage(message)
          await send(message)
        }}
      >
        <input type="text" name="message" />
        <button type="submit">Send</button>
      </form>
    </div>
  )
}

تعيين ملفات تعريف الارتباط (Cookies)

يمكنك تعيين ملفات تعريف الارتباط داخل إجراء خادم باستخدام دالة cookies:

'use server'

import { cookies } from 'next/headers'

export async function create() {
  const cart = await createCart()
  cookies().set('cartId', cart.id)
}
'use server'

import { cookies } from 'next/headers'

export async function create() {
  const cart = await createCart()
  cookies().set('cartId', cart.id)
}

قراءة ملفات تعريف الارتباط (Cookies)

يمكنك قراءة ملفات تعريف الارتباط داخل إجراء خادم باستخدام دالة cookies:

'use server'

import { cookies } from 'next/headers'

export async function read() {
  const auth = cookies().get('authorization')?.value
  // ...
}
'use server'

import { cookies } from 'next/headers'

export async function read() {
  const auth = cookies().get('authorization')?.value
  // ...
}

حذف ملفات تعريف الارتباط (Cookies)

يمكنك حذف ملفات تعريف الارتباط داخل إجراء خادم باستخدام دالة cookies:

'use server'

import { cookies } from 'next/headers'

export async function delete() {
  cookies().delete('name')
  // ...
}
'use server'

import { cookies } from 'next/headers'

export async function delete() {
  cookies().delete('name')
  // ...
}

راجع أمثلة إضافية لحذف ملفات تعريف الارتباط من إجراءات الخادم.