Форма (Form)

Компонент <Form> расширяет HTML-элемент <form>, предоставляя префетчинг для UI загрузки, клиентскую навигацию при отправке и прогрессивное улучшение.

Он особенно полезен для форм, обновляющих параметры поиска в URL, так как сокращает количество шаблонного кода.

Базовое использование:

import Form from 'next/form'

export default function Page() {
  return (
    <Form action="/search">
      {/* При отправке значение input будет добавлено к
          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">
      {/* При отправке значение input будет добавлено к
          URL, например /search?query=abc */}
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}

Справочник

Поведение компонента <Form> зависит от того, передается ли в проп action строка (string) или функция (function).

  • Когда action — это строка, <Form> ведет себя как нативная HTML-форма с методом GET. Данные формы кодируются в URL как параметры поиска, и при отправке формы происходит переход по указанному URL. Дополнительно Next.js:
    • Префетчит путь, когда форма становится видимой, предзагружая общий UI (например, layout.js и loading.js), что ускоряет навигацию.
    • Выполняет клиентскую навигацию вместо полной перезагрузки страницы, сохраняя общий UI и клиентское состояние.
  • Когда action — это функция (Server Action), <Form> ведет себя как React-форма, выполняя действие при отправке формы.

Пропсы action (string)

Когда action — это строка, компонент <Form> поддерживает следующие пропсы:

ПропсПримерТипОбязательный
actionaction="/search"string (URL или относительный путь)Да
replacereplace={false}boolean-
scrollscroll={true}boolean-
prefetchprefetch={true}boolean-
  • action: URL или путь для перехода при отправке формы.
    • Пустая строка "" перейдет на тот же маршрут с обновленными параметрами поиска.
  • replace: Заменяет текущее состояние истории вместо добавления нового в стек истории браузера. По умолчанию false.
  • scroll: Управляет поведением прокрутки при навигации. По умолчанию true — прокручивает к верху нового маршрута и сохраняет позицию прокрутки при навигации назад/вперед.
  • prefetch: Управляет префетчингом пути, когда форма становится видимой в области просмотра пользователя. По умолчанию true.

Пропсы action (function)

Когда action — это функция, компонент <Form> поддерживает следующий пропс:

ПропсПримерТипОбязательный
actionaction={myAction}function (Server Action)Да

Важно: Когда 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 могут использоваться для переопределения соответствующих пропсов, но их использование приведет к нативному поведению браузера.
    • Если эти пропсы необходимы, используйте HTML-элемент <form>.
  • <input type="file">: Использование этого типа input при строковом 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> становится видимой в области просмотра, общий UI (например, layout.js и loading.js) на странице /search будет префетчен. При отправке форма немедленно перейдет на новый маршрут и покажет UI загрузки во время получения результатов. Fallback UI можно настроить с помощью loading.js:

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

Для случаев, когда общий UI еще не загружен, можно показать мгновенную обратную связь с помощью 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> не может автоматически префетчить общий UI.

'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.