Formularios y mutaciones
Los formularios te permiten crear y actualizar datos en aplicaciones web. Next.js ofrece una forma poderosa de manejar envíos de formularios y mutaciones de datos usando Rutas de API.
Es bueno saber:
- Pronto recomendaremos adoptar incrementalmente el Enrutador de Aplicación (App Router) y usar Acciones de Servidor (Server Actions) para manejar envíos de formularios y mutaciones de datos. Las Acciones de Servidor te permiten definir funciones asíncronas en el servidor que pueden ser llamadas directamente desde tus componentes, sin necesidad de crear manualmente una Ruta de API.
- Las Rutas de API no especifican cabeceras CORS, lo que significa que por defecto solo permiten solicitudes del mismo origen.
- Dado que las Rutas de API se ejecutan en el servidor, podemos usar valores sensibles (como claves de API) a través de Variables de Entorno sin exponerlos al cliente. Esto es crítico para la seguridad de tu aplicación.
Ejemplos
Formularios Exclusivos del Servidor
Con el Enrutador de Páginas, necesitas crear manualmente endpoints de API para manejar de forma segura mutaciones de datos en el servidor.
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 })
}
Luego, llama a la Ruta de API desde el cliente con un manejador de eventos:
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,
})
// Maneja la respuesta si es necesario
const data = await response.json()
// ...
}
return (
<form onSubmit={onSubmit}>
<input type="text" name="name" />
<button type="submit">Enviar</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,
})
// Maneja la respuesta si es necesario
const data = await response.json()
// ...
}
return (
<form onSubmit={onSubmit}>
<input type="text" name="name" />
<button type="submit">Enviar</button>
</form>
)
}
Redireccionando
Si deseas redirigir al usuario a una ruta diferente después de una mutación, puedes usar redirect
a cualquier URL absoluta o relativa:
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}`)
}
Validación de Formularios
Recomendamos usar validación HTML como required
y type="email"
para validación básica de formularios.
Para validación más avanzada en el servidor, usa una biblioteca de validación de esquemas como zod para validar la estructura de los datos del formulario parseados:
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)
// ...
}
Mostrando Estado de Carga
Puedes usar el estado de React para mostrar un estado de carga cuando un formulario se está enviando al servidor:
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) // Establece carga a true cuando comienza la solicitud
try {
const formData = new FormData(event.currentTarget)
const response = await fetch('/api/submit', {
method: 'POST',
body: formData,
})
// Maneja la respuesta si es necesario
const data = await response.json()
// ...
} catch (error) {
// Maneja el error si es necesario
console.error(error)
} finally {
setIsLoading(false) // Establece carga a false cuando completa la solicitud
}
}
return (
<form onSubmit={onSubmit}>
<input type="text" name="name" />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Cargando...' : 'Enviar'}
</button>
</form>
)
}
import React, { useState } from 'react'
export default function Page() {
const [isLoading, setIsLoading] = useState(false)
async function onSubmit(event) {
event.preventDefault()
setIsLoading(true) // Establece carga a true cuando comienza la solicitud
try {
const formData = new FormData(event.currentTarget)
const response = await fetch('/api/submit', {
method: 'POST',
body: formData,
})
// Maneja la respuesta si es necesario
const data = await response.json()
// ...
} catch (error) {
// Maneja el error si es necesario
console.error(error)
} finally {
setIsLoading(false) // Establece carga a false cuando completa la solicitud
}
}
return (
<form onSubmit={onSubmit}>
<input type="text" name="name" />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Cargando...' : 'Enviar'}
</button>
</form>
)
}
Manejo de Errores
Puedes usar el estado de React para mostrar un mensaje de error cuando falla el envío de un formulario:
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) // Limpiar errores previos cuando comienza una nueva solicitud
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.')
}
// Manejar la respuesta si es necesario
const data = await response.json()
// ...
} catch (error) {
// Capturar el mensaje de error para mostrar al usuario
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) // Limpiar errores previos cuando comienza una nueva solicitud
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.')
}
// Manejar la respuesta si es necesario
const data = await response.json()
// ...
} catch (error) {
// Capturar el mensaje de error para mostrar al usuario
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>
)
}
Configuración de Cookies
Puedes configurar cookies dentro de una Ruta API usando el método setHeader
en la respuesta:
Lectura de Cookies
Puedes leer cookies dentro de una Ruta API usando el helper de solicitud cookies
:
Eliminación de Cookies
Puedes eliminar cookies dentro de una Ruta API usando el método setHeader
en la respuesta: