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

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

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

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

أمثلة

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

مع موجه الصفحات، تحتاج إلى إنشاء نقاط نهاية 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>
  )
}

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

إذا كنت ترغب في إعادة توجيه المستخدم إلى مسار مختلف بعد التحويل، يمكنك 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}`)
}

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

نوصي باستخدام التحقق من صحة 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)

  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>
  )
}

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

يمكنك استخدام حالة (state) 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) // Clear previous errors when a new request starts

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

      if (!response.ok) {
        throw new Error('Failed to submit the data. Please try again.')
      }

      // Handle response if necessary
      const data = await response.json()
      // ...
    } catch (error) {
      // Capture the error message to display to the user
      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 ? 'Loading...' : 'Submit'}
        </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) // Clear previous errors when a new request starts

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

      if (!response.ok) {
        throw new Error('Failed to submit the data. Please try again.')
      }

      // Handle response if necessary
      const data = await response.json()
      // ...
    } catch (error) {
      // Capture the error message to display to the user
      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 ? 'Loading...' : 'Submit'}
        </button>
      </form>
    </div>
  )
}

تعيين ملفات تعريف الارتباط (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('Cookie has been set.')
}
export default async function handler(req, res) {
  res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
  res.status(200).send('Cookie has been set.')
}

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

يمكنك قراءة ملفات تعريف الارتباط داخل مسار 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
  // ...
}

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

يمكنك حذف ملفات تعريف الارتباط داخل مسار 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('Cookie has been deleted.')
}
export default async function handler(req, res) {
  res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
  res.status(200).send('Cookie has been deleted.')
}