Formulario

El componente <Form> extiende el elemento HTML <form> para proporcionar precarga (prefetching) de interfaz de carga (loading UI), navegación del lado del cliente (client-side navigation) al enviar, y mejora progresiva (progressive enhancement).

Es útil para formularios que actualizan parámetros de búsqueda en la URL, ya que reduce el código repetitivo necesario para lograr lo anterior.

Uso básico:

import Form from 'next/form'

export default function Page() {
  return (
    <Form action="/search">
      {/* Al enviar, el valor del input se añadirá a
          la URL, ej. /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">
      {/* Al enviar, el valor del input se añadirá a
          la URL, ej. /search?query=abc */}
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}

Referencia

El comportamiento del componente <Form> depende de si la prop action recibe un string o una function.

  • Cuando action es un string, el <Form> se comporta como un formulario HTML nativo que usa un método GET. Los datos del formulario se codifican en la URL como parámetros de búsqueda, y cuando se envía el formulario, navega a la URL especificada. Además, Next.js:
  • Cuando action es una función (Server Action), <Form> se comporta como un formulario React, ejecutando la acción cuando se envía el formulario.

Props de action (string)

Cuando action es un string, el componente <Form> soporta las siguientes props:

PropEjemploTipoRequerido
actionaction="/search"string (URL o ruta relativa)
replacereplace={false}boolean-
scrollscroll={true}boolean-
prefetchprefetch={true}boolean-
  • action: La URL o ruta a la que navegar cuando se envía el formulario.
    • Un string vacío "" navegará a la misma ruta con los parámetros de búsqueda actualizados.
  • replace: Reemplaza el estado actual del historial en lugar de añadir uno nuevo a la pila del historial del navegador. Por defecto es false.
  • scroll: Controla el comportamiento de desplazamiento durante la navegación. Por defecto es true, lo que significa que se desplazará al inicio de la nueva ruta y mantendrá la posición de desplazamiento para navegación hacia atrás y adelante.
  • prefetch: Controla si la ruta debe ser precargada cuando el formulario se hace visible en el viewport del usuario. Por defecto es true.

Props de action (función)

Cuando action es una función, el componente <Form> soporta la siguiente prop:

PropEjemploTipoRequerido
actionaction={myAction}function (Server Action)
  • action: La Server Action que se llamará cuando se envíe el formulario. Consulte la documentación de React para más información.

Nota importante: Cuando action es una función, las props replace y scroll se ignoran.

Consideraciones

  • formAction: Puede usarse en campos <button> o <input type="submit"> para sobrescribir la prop action. Next.js realizará una navegación del lado del cliente, sin embargo, este enfoque no soporta precarga.
    • Al usar basePath, también debe incluirse en la ruta de formAction. Ej. formAction="/base-path/search".
  • key: No se soporta pasar una prop key a un action de tipo string. Si desea forzar un re-renderizado o realizar una mutación, considere usar un action de tipo función.
  • onSubmit: Puede usarse para manejar lógica de envío de formularios. Sin embargo, llamar a event.preventDefault() sobrescribirá el comportamiento de <Form> como navegar a la URL especificada.
  • method, encType, target: No están soportados ya que sobrescriben el comportamiento de <Form>.
    • Similarmente, formMethod, formEncType y formTarget pueden usarse para sobrescribir las props method, encType y target respectivamente, y su uso hará que se recurra al comportamiento nativo del navegador.
    • Si necesita usar estas props, utilice el elemento HTML <form> en su lugar.
  • <input type="file">: Usar este tipo de input cuando action es un string coincidirá con el comportamiento del navegador al enviar el nombre del archivo en lugar del objeto de archivo.

Ejemplos

Formulario de búsqueda que lleva a una página de resultados

Puede crear un formulario de búsqueda que navegue a una página de resultados pasando la ruta como 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>
  )
}

Cuando el usuario actualiza el campo de búsqueda y envía el formulario, los datos del formulario se codificarán en la URL como parámetros de búsqueda, ej. /search?query=abc.

Nota importante: Si pasa un string vacío "" a action, el formulario navegará a la misma ruta con los parámetros de búsqueda actualizados.

En la página de resultados, puede acceder a la consulta usando la prop searchParams de page.js y usarla para obtener datos de una fuente externa.

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

Cuando el <Form> se hace visible en el viewport del usuario, la UI compartida (como layout.js y loading.js) en la página /search se precargará. Al enviar, el formulario navegará inmediatamente a la nueva ruta y mostrará la UI de carga mientras se obtienen los resultados. Puede diseñar la UI de respaldo usando loading.js:

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

Para cubrir casos cuando la UI compartida aún no se ha cargado, puede mostrar retroalimentación instantánea al usuario usando useFormStatus.

Primero, cree un componente que muestre un estado de carga cuando el formulario está pendiente:

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

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

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

Luego, actualice la página del formulario de búsqueda para usar el componente 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>
  )
}

Mutaciones con Server Actions

Puede realizar mutaciones pasando una función a la prop 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">Crear 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">Crear Post</button>
    </Form>
  )
}

Después de una mutación, es común redirigir al nuevo recurso. Puede usar la función redirect de next/navigation para navegar a la página del nuevo post.

Nota importante: Como el "destino" del envío del formulario no se conoce hasta que se ejecuta la acción, <Form> no puede precargar automáticamente la UI compartida.

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

export async function createPost(formData: FormData) {
  // Crear un nuevo post
  // ...

  // Redirigir al nuevo post
  redirect(`/posts/${data.id}`)
}
'use server'
import { redirect } from 'next/navigation'

export async function createPost(formData) {
  // Crear un nuevo post
  // ...

  // Redirigir al nuevo post
  redirect(`/posts/${data.id}`)
}

Luego, en la nueva página, puede obtener datos usando la prop 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>
  )
}

Consulte la documentación de Server Actions para más ejemplos.