النموذج (Form)

يمتد مكون <Form> عنصر HTML <form> لتوفير التحميل المسبق (prefetching) لواجهة التحميل (loading UI)، التنقل من جانب العميل (client-side navigation) عند الإرسال، والتحسين التدريجي (progressive enhancement).

هو مفيد للنماذج التي تقوم بتحديث معلمات البحث في URL لأنه يقلل من الكود المتكرر المطلوب لتحقيق ما سبق.

الاستخدام الأساسي:

import Form from 'next/form'

export default function Page() {
  return (
    <Form action="/search">
      {/* عند الإرسال، سيتم إضافة قيمة الإدخال إلى
          الـ URL، مثلاً /search?query=abc */}
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}
import Form from 'next/form'

export default function Search() {
  return (
    <Form action="/search">
      {/* عند الإرسال، سيتم إضافة قيمة الإدخال إلى
          الـ URL، مثلاً /search?query=abc */}
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}

المرجع

يعتمد سلوك مكون <Form> على ما إذا كانت الخاصية action تمرر كـ string أو function.

  • عندما تكون action سلسلة نصية (string)، يتصرف <Form> مثل نموذج HTML عادي يستخدم طريقة GET. يتم ترميز بيانات النموذج في الـ URL كمعلمات بحث، وعند إرسال النموذج، يتم التنقل إلى الـ URL المحدد. بالإضافة إلى ذلك، يقوم Next.js بما يلي:
    • التحميل المسبق (Prefetches) للمسار عندما يصبح النموذج مرئيًا، مما يؤدي إلى تحميل مسبق لواجهة المستخدم المشتركة (مثل layout.js و loading.js)، مما يؤدي إلى تنقل أسرع.
    • يقوم بـ التنقل من جانب العميل (client-side navigation) بدلاً من إعادة تحميل الصفحة بالكامل عند إرسال النموذج. هذا يحافظ على واجهة المستخدم المشتركة وحالة جانب العميل.
  • عندما تكون action دالة (function) (إجراء خادم Server Action)، يتصرف <Form> مثل نموذج React، بتنفيذ الإجراء عند إرسال النموذج.

خصائص action (سلسلة نصية)

عندما تكون action سلسلة نصية، يدعم مكون <Form> الخصائص التالية:

الخاصيةمثالالنوعمطلوب
actionaction="/search"string (URL أو مسار نسبي)نعم
replacereplace={false}boolean-
scrollscroll={true}boolean-
prefetchprefetch={true}boolean-
  • action: الـ URL أو المسار للتنقل إليه عند إرسال النموذج.
    • سلسلة نصية فارغة "" ستؤدي إلى التنقل إلى نفس المسار مع تحديث معلمات البحث.
  • replace: يستبدل حالة التاريخ الحالية بدلاً من إضافة جديدة إلى مكدس تاريخ المتصفح. الافتراضي هو false.
  • scroll: يتحكم في سلوك التمرير أثناء التنقل. الافتراضي هو true، مما يعني أنه سيتم التمرير إلى أعلى المسار الجديد، والحفاظ على موضع التمرير للتنقل للخلف وللأمام.
  • prefetch: يتحكم فيما إذا كان يجب تحميل المسار مسبقًا عندما يصبح النموذج مرئيًا في نطاق رؤية المستخدم. الافتراضي هو true.

خصائص action (دالة)

عندما تكون action دالة، يدعم مكون <Form> الخاصية التالية:

الخاصيةمثالالنوعمطلوب
actionaction={myAction}function (إجراء خادم)نعم
  • action: إجراء الخادم الذي سيتم استدعاؤه عند إرسال النموذج. راجع وثائق React للمزيد.

جيد معرفته: عندما تكون action دالة، يتم تجاهل خصائص replace و scroll.

محاذير

  • formAction: يمكن استخدامها في حقول <button> أو <input type="submit"> لتجاوز خاصية action. سيقوم Next.js بتنفيذ تنقل من جانب العميل، لكن هذا الأسلوب لا يدعم التحميل المسبق.
    • عند استخدام basePath، يجب أيضًا تضمينه في مسار formAction. مثلاً formAction="/base-path/search".
  • key: تمرير خاصية key إلى action من نوع سلسلة نصية غير مدعوم. إذا كنت ترغب في إطلاق إعادة عرض أو تنفيذ تغيير، فكر في استخدام action كدالة بدلاً من ذلك.
  • onSubmit: يمكن استخدامه لمعالجة منطق إرسال النموذج. ومع ذلك، استدعاء event.preventDefault() سيتجاوز سلوك <Form> مثل التنقل إلى الـ URL المحدد.
  • method, encType, target: غير مدعومة لأنها تتجاوز سلوك <Form>.
    • بالمثل، يمكن استخدام formMethod، formEncType، و formTarget لتجاوز خصائص method، encType، و target على التوالي، واستخدامها سيعيد إلى سلوك المتصفح الافتراضي.
    • إذا كنت بحاجة إلى استخدام هذه الخصائص، استخدم عنصر HTML <form> بدلاً من ذلك.
  • <input type="file">: استخدام نوع الإدخال هذا عندما تكون action سلسلة نصية سيطابق سلوك المتصفح بإرسال اسم الملف بدلاً من كائن الملف.

أمثلة

نموذج بحث يؤدي إلى صفحة نتائج البحث

يمكنك إنشاء نموذج بحث يؤدي إلى صفحة نتائج البحث عن طريق تمرير المسار كـ action:

import Form from 'next/form'

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}
import Form from 'next/form'

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}

عندما يقوم المستخدم بتحديث حقل إدخال الاستعلام وإرسال النموذج، سيتم ترميز بيانات النموذج في الـ URL كمعلمات بحث، مثلاً /search?query=abc.

جيد معرفته: إذا قمت بتمرير سلسلة نصية فارغة "" إلى action، سينتقل النموذج إلى نفس المسار مع تحديث معلمات البحث.

في صفحة النتائج، يمكنك الوصول إلى الاستعلام باستخدام خاصية searchParams لـ page.js واستخدامها لجلب البيانات من مصدر خارجي.

import { getSearchResults } from '@/lib/search'

export default async function SearchPage({
  searchParams,
}: {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
  const results = await getSearchResults((await searchParams).query)

  return <div>...</div>
}
import { getSearchResults } from '@/lib/search'

export default async function SearchPage({ searchParams }) {
  const results = await getSearchResults((await searchParams).query)

  return <div>...</div>
}

عندما يصبح <Form> مرئيًا في نطاق رؤية المستخدم، سيتم تحميل واجهة المستخدم المشتركة (مثل layout.js و loading.js) في صفحة /search مسبقًا. عند الإرسال، سينتقل النموذج فورًا إلى المسار الجديد ويعرض واجهة تحميل أثناء جلب النتائج. يمكنك تصميم واجهة الانتظار باستخدام loading.js:

export default function Loading() {
  return <div>Loading...</div>
}
export default function Loading() {
  return <div>Loading...</div>
}

لتغطية الحالات عندما لم يتم تحميل واجهة المستخدم المشتركة بعد، يمكنك عرض ردود فعل فورية للمستخدم باستخدام useFormStatus.

أولاً، قم بإنشاء مكون يعرض حالة تحميل عندما يكون النموذج قيد الانتظار:

'use client'
import { useFormStatus } from 'react-dom'

export default function SearchButton() {
  const status = useFormStatus()
  return (
    <button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
  )
}
'use client'
import { useFormStatus } from 'react-dom'

export default function SearchButton() {
  const status = useFormStatus()
  return (
    <button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
  )
}

ثم، قم بتحديث صفحة نموذج البحث لاستخدام مكون SearchButton:

import Form from 'next/form'
import { SearchButton } from '@/ui/search-button'

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <SearchButton />
    </Form>
  )
}
import Form from 'next/form'
import { SearchButton } from '@/ui/search-button'

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <SearchButton />
    </Form>
  )
}

التغييرات باستخدام إجراءات الخادم (Server Actions)

يمكنك تنفيذ التغييرات عن طريق تمرير دالة إلى خاصية action.

import Form from 'next/form'
import { createPost } from '@/posts/actions'

export default function Page() {
  return (
    <Form action={createPost}>
      <input name="title" />
      {/* ... */}
      <button type="submit">Create Post</button>
    </Form>
  )
}
import Form from 'next/form'
import { createPost } from '@/posts/actions'

export default function Page() {
  return (
    <Form action={createPost}>
      <input name="title" />
      {/* ... */}
      <button type="submit">Create Post</button>
    </Form>
  )
}

بعد التغيير، من الشائع إعادة التوجيه إلى المورد الجديد. يمكنك استخدام دالة redirect من next/navigation للتنقل إلى صفحة المنشور الجديد.

جيد معرفته: بما أن "الوجهة" لإرسال النموذج غير معروفة حتى يتم تنفيذ الإجراء، لا يمكن لـ <Form> تحميل واجهة المستخدم المشتركة مسبقًا تلقائيًا.

'use server'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  // إنشاء منشور جديد
  // ...

  // إعادة التوجيه إلى المنشور الجديد
  redirect(`/posts/${data.id}`)
}
'use server'
import { redirect } from 'next/navigation'

export async function createPost(formData) {
  // إنشاء منشور جديد
  // ...

  // إعادة التوجيه إلى المنشور الجديد
  redirect(`/posts/${data.id}`)
}

ثم، في الصفحة الجديدة، يمكنك جلب البيانات باستخدام خاصية params:

import { getPost } from '@/posts/data'

export default async function PostPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const data = await getPost(id)

  return (
    <div>
      <h1>{data.title}</h1>
      {/* ... */}
    </div>
  )
}
import { getPost } from '@/posts/data'

export default async function PostPage({ params }) {
  const { id } = await params
  const data = await getPost(id)

  return (
    <div>
      <h1>{data.title}</h1>
      {/* ... */}
    </div>
  )
}

راجع وثائق إجراءات الخادم (Server Actions) للمزيد من الأمثلة.