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

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

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

  • سنوصي قريبًا باعتماد تدريجي لموجه التطبيق (App Router) واستخدام إجراءات الخادم (Server Actions) للتعامل مع إرسال النماذج وتحويل البيانات. تسمح إجراءات الخادم بتعريف دوال غير متزامنة على الخادم يمكن استدعاؤها مباشرة من مكوناتك، دون الحاجة إلى إنشاء مسار API يدويًا.
  • لا تحدد مسارات API رؤوس CORS، مما يعني أنها تعمل فقط من نفس المصدر افتراضيًا.
  • نظرًا لأن مسارات API تعمل على الخادم، يمكننا استخدام قيم حساسة (مثل مفاتيح API) عبر متغيرات البيئة (Environment Variables) دون الكشف عنها للعميل. هذا أمر بالغ الأهمية لأمان تطبيقك.

أمثلة

نموذج يعمل على الخادم فقط

مع موجه الصفحات (Pages Router)، تحتاج إلى إنشاء نقاط نهاية API يدويًا للتعامل مع تحويل البيانات على الخادم بشكل آمن.

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const data = req.body
  const id = await createItem(data)
  res.status(200).json({ id })
}
export default function handler(req, res) {
  const data = req.body
  const id = await createItem(data)
  res.status(200).json({ id })
}

ثم استدع مسار API من العميل باستخدام معالج الأحداث:

import { FormEvent } from 'react'

export default function Page() {
  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    const formData = new FormData(event.currentTarget)
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: formData,
    })

    // تعامل مع الاستجابة إذا لزم الأمر
    const data = await response.json()
    // ...
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit">إرسال</button>
    </form>
  )
}
export default function Page() {
  async function onSubmit(event) {
    event.preventDefault()

    const formData = new FormData(event.target)
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: formData,
    })

    // تعامل مع الاستجابة إذا لزم الأمر
    const data = await response.json()
    // ...
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit">إرسال</button>
    </form>
  )
}

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

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

للتحقق المتقدم من صحة النموذج على جانب الخادم، يمكنك استخدام مكتبة تحقق المخططات مثل zod للتحقق من حقول النموذج قبل تحويل البيانات:

import type { NextApiRequest, NextApiResponse } from 'next'
import { z } from 'zod'

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

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const parsed = schema.parse(req.body)
  // ...
}
import { z } from 'zod'

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

export default async function handler(req, res) {
  const parsed = schema.parse(req.body)
  // ...
}

التعامل مع الأخطاء

يمكنك استخدام حالة React لعرض رسالة خطأ عند فشل إرسال النموذج:

import React, { useState, FormEvent } from 'react'

export default function Page() {
  const [isLoading, setIsLoading] = useState<boolean>(false)
  const [error, setError] = useState<string | null>(null)

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setIsLoading(true)
    setError(null) // مسح الأخطاء السابقة عند بدء طلب جديد

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      if (!response.ok) {
        throw new Error('فشل في إرسال البيانات. يرجى المحاولة مرة أخرى.')
      }

      // تعامل مع الاستجابة إذا لزم الأمر
      const data = await response.json()
      // ...
    } catch (error) {
      // التقاط رسالة الخطأ لعرضها للمستخدم
      setError(error.message)
      console.error(error)
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div>
      {error && <div style={{ color: 'red' }}>{error}</div>}
      <form onSubmit={onSubmit}>
        <input type="text" name="name" />
        <button type="submit" disabled={isLoading}>
          {isLoading ? 'جاري التحميل...' : 'إرسال'}
        </button>
      </form>
    </div>
  )
}
import React, { useState } from 'react'

export default function Page() {
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState(null)

  async function onSubmit(event) {
    event.preventDefault()
    setIsLoading(true)
    setError(null) // مسح الأخطاء السابقة عند بدء طلب جديد

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      if (!response.ok) {
        throw new Error('فشل في إرسال البيانات. يرجى المحاولة مرة أخرى.')
      }

      // تعامل مع الاستجابة إذا لزم الأمر
      const data = await response.json()
      // ...
    } catch (error) {
      // التقاط رسالة الخطأ لعرضها للمستخدم
      setError(error.message)
      console.error(error)
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div>
      {error && <div style={{ color: 'red' }}>{error}</div>}
      <form onSubmit={onSubmit}>
        <input type="text" name="name" />
        <button type="submit" disabled={isLoading}>
          {isLoading ? 'جاري التحميل...' : 'إرسال'}
        </button>
      </form>
    </div>
  )
}

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

يمكنك استخدام حالة React لعرض حالة التحميل عند إرسال النموذج على الخادم:

import React, { useState, FormEvent } from 'react'

export default function Page() {
  const [isLoading, setIsLoading] = useState<boolean>(false)

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setIsLoading(true) // تعيين التحميل إلى true عند بدء الطلب

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      // تعامل مع الاستجابة إذا لزم الأمر
      const data = await response.json()
      // ...
    } catch (error) {
      // تعامل مع الخطأ إذا لزم الأمر
      console.error(error)
    } finally {
      setIsLoading(false) // تعيين التحميل إلى false عند اكتمال الطلب
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'جاري التحميل...' : 'إرسال'}
      </button>
    </form>
  )
}
import React, { useState } from 'react'

export default function Page() {
  const [isLoading, setIsLoading] = useState(false)

  async function onSubmit(event) {
    event.preventDefault()
    setIsLoading(true) // تعيين التحميل إلى true عند بدء الطلب

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      // تعامل مع الاستجابة إذا لزم الأمر
      const data = await response.json()
      // ...
    } catch (error) {
      // تعامل مع الخطأ إذا لزم الأمر
      console.error(error)
    } finally {
      setIsLoading(false) // تعيين التحميل إلى false عند اكتمال الطلب
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'جاري التحميل...' : 'إرسال'}
      </button>
    </form>
  )
}

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

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

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const id = await addPost()
  res.redirect(307, `/post/${id}`)
}
export default async function handler(req, res) {
  const id = await addPost()
  res.redirect(307, `/post/${id}`)
}

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

يمكنك تعيين ملفات تعريف الارتباط داخل مسار API باستخدام طريقة setHeader على الاستجابة:

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
  res.status(200).send('تم تعيين ملف تعريف الارتباط.')
}
export default async function handler(req, res) {
  res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
  res.status(200).send('تم تعيين ملف تعريف الارتباط.')
}

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

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

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const auth = req.cookies.authorization
  // ...
}
export default async function handler(req, res) {
  const auth = req.cookies.authorization
  // ...
}

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

يمكنك حذف ملفات تعريف الارتباط داخل مسار API باستخدام طريقة setHeader على الاستجابة:

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
  res.status(200).send('تم حذف ملف تعريف الارتباط.')
}
export default async function handler(req, res) {
  res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
  res.status(200).send('تم حذف ملف تعريف الارتباط.')
}