Formularios y Mutaciones
Los formularios permiten crear y actualizar datos en aplicaciones web. Next.js proporciona 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 App Router y usar Acciones de Servidor (Server Actions) para manejar envíos de formularios y mutaciones de datos. Las Acciones de Servidor 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 son solo del mismo origen por defecto.
- 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
Formulario solo en servidor
Con el Pages Router, necesitas crear manualmente endpoints de API para manejar de forma segura la mutación 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,
})
// Manejar 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,
})
// Manejar 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>
)
}
Validación de formularios
Recomendamos usar validación HTML como required
y type="email"
para validación básica en el cliente.
Para validación más avanzada en el servidor, puedes usar una biblioteca de validación de esquemas como zod para validar los campos del formulario antes de mutar los datos:
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)
// ...
}
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('Error al enviar los datos. Por favor, inténtalo de nuevo.')
}
// 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 ? 'Cargando...' : 'Enviar'}
</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('Error al enviar los datos. Por favor, inténtalo de nuevo.')
}
// 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 ? 'Cargando...' : 'Enviar'}
</button>
</form>
</div>
)
}
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) // Establecer carga a true cuando comienza la solicitud
try {
const formData = new FormData(event.currentTarget)
const response = await fetch('/api/submit', {
method: 'POST',
body: formData,
})
// Manejar la respuesta si es necesario
const data = await response.json()
// ...
} catch (error) {
// Manejar error si es necesario
console.error(error)
} finally {
setIsLoading(false) // Establecer carga a false cuando la solicitud se completa
}
}
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) // Establecer carga a true cuando comienza la solicitud
try {
const formData = new FormData(event.currentTarget)
const response = await fetch('/api/submit', {
method: 'POST',
body: formData,
})
// Manejar la respuesta si es necesario
const data = await response.json()
// ...
} catch (error) {
// Manejar error si es necesario
console.error(error)
} finally {
setIsLoading(false) // Establecer carga a false cuando la solicitud se completa
}
}
return (
<form onSubmit={onSubmit}>
<input type="text" name="name" />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Cargando...' : 'Enviar'}
</button>
</form>
)
}
Redireccionamiento
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}`)
}
Configuración de cookies
Puedes establecer cookies dentro de una Ruta de API usando el método setHeader
en la respuesta:
Lectura de cookies
Puedes leer cookies dentro de una Ruta de API usando el ayudante de solicitud cookies
:
Eliminación de cookies
Puedes eliminar cookies dentro de una Ruta de API usando el método setHeader
en la respuesta: