Formulaires et Mutations
Les formulaires vous permettent de créer et mettre à jour des données dans les applications web. Next.js offre une solution puissante pour gérer les soumissions de formulaires et les mutations de données en utilisant les Actions Serveur.
Fonctionnement des Actions Serveur
Avec les Actions Serveur, vous n'avez pas besoin de créer manuellement des points d'API. À la place, vous définissez des fonctions serveur asynchrones qui peuvent être appelées directement depuis vos composants.
🎥 Regarder : En savoir plus sur les formulaires et mutations avec le routeur App → YouTube (10 minutes).
Les Actions Serveur peuvent être définies dans des Composants Serveur ou appelées depuis des Composants Client. Définir l'action dans un Composant Serveur permet au formulaire de fonctionner sans JavaScript, offrant une amélioration progressive.
Activez les Actions Serveur dans votre fichier next.config.js
:
module.exports = {
experimental: {
serverActions: true,
},
}
Bon à savoir :
- Les formulaires appelant des Actions Serveur depuis des Composants Serveur peuvent fonctionner sans JavaScript.
- Les formulaires appelant des Actions Serveur depuis des Composants Client mettront les soumissions en file d'attente si JavaScript n'est pas encore chargé, en priorisant l'hydratation client.
- Les Actions Serveur héritent du runtime de la page ou du layout dans lequel elles sont utilisées.
- Les Actions Serveur fonctionnent avec des routes entièrement statiques (y compris la revalidation de données avec ISR).
Revalidation des Données en Cache
Les Actions Serveur s'intègrent profondément avec l'architecture de cache et revalidation de Next.js. Lorsqu'un formulaire est soumis, l'Action Serveur peut mettre à jour les données en cache et revalider toutes les clés de cache qui doivent changer.
Plutôt que d'être limité à un seul formulaire par route comme dans les applications traditionnelles, les Actions Serveur permettent d'avoir plusieurs actions par route. De plus, le navigateur n'a pas besoin de s'actualiser lors de la soumission d'un formulaire. En un seul aller-retour réseau, Next.js peut retourner à la fois l'UI mise à jour et les données rafraîchies.
Consultez les exemples ci-dessous pour revalider des données depuis des Actions Serveur.
Exemples
Formulaires Serveur Uniquement
Pour créer un formulaire serveur uniquement, définissez l'Action Serveur dans un Composant Serveur. L'action peut être définie en ligne avec la directive "use server"
en haut de la fonction, ou dans un fichier séparé avec la directive en haut du fichier.
export default function Page() {
async function create(formData: FormData) {
'use server'
// muter les données
// revalider le cache
}
return <form action={create}>...</form>
}
export default function Page() {
async function create(formData) {
'use server'
// muter les données
// revalider le cache
}
return <form action={create}>...</form>
}
Bon à savoir :
<form action={create}>
prend le type de données FormData. Dans l'exemple ci-dessus, le FormData soumis via leform
HTML est accessible dans l'action serveurcreate
.
Revalidation des Données
Les Actions Serveur vous permettent d'invalider le Cache Next.js à la demande. Vous pouvez invalider un segment de route entier avec revalidatePath
:
'use server'
import { revalidatePath } from 'next/cache'
export default async function submit() {
await submitForm()
revalidatePath('/')
}
'use server'
import { revalidatePath } from 'next/cache'
export default async function submit() {
await submitForm()
revalidatePath('/')
}
Ou invalider une récupération de données spécifique avec une étiquette de cache en utilisant revalidateTag
:
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('posts')
}
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('posts')
}
Redirection
Si vous souhaitez rediriger l'utilisateur vers une route différente après l'exécution d'une Action Serveur, vous pouvez utiliser redirect
et n'importe quelle URL absolue ou relative :
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export default async function submit() {
const id = await addPost()
revalidateTag('posts') // Mettre à jour les posts en cache
redirect(`/post/${id}`) // Naviguer vers la nouvelle route
}
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export default async function submit() {
const id = await addPost()
revalidateTag('posts') // Mettre à jour les posts en cache
redirect(`/post/${id}`) // Naviguer vers la nouvelle route
}
Validation de Formulaire
Nous recommandons d'utiliser la validation HTML comme required
et type="email"
pour une validation de formulaire basique.
Pour une validation côté serveur plus avancée, utilisez une bibliothèque de validation de schéma comme zod pour valider la structure des données de formulaire analysées :
import { z } from 'zod'
const schema = z.object({
// ...
})
export default async function submit(formData: FormData) {
const parsed = schema.parse({
id: formData.get('id'),
})
// ...
}
import { z } from 'zod'
const schema = z.object({
// ...
})
export default async function submit(formData) {
const parsed = schema.parse({
id: formData.get('id'),
})
// ...
}
Affichage de l'État de Chargement
Utilisez le hook useFormStatus
pour afficher un état de chargement lorsqu'un formulaire est en cours de soumission sur le serveur. Le hook useFormStatus
ne peut être utilisé que comme enfant d'un élément form
utilisant une Action Serveur.
Par exemple, le bouton de soumission suivant :
<SubmitButton />
peut ensuite être utilisé dans un formulaire avec une Action Serveur :
import { SubmitButton } from '@/app/submit-button'
export default async function Home() {
return (
<form action={...}>
<input type="text" name="field-name" />
<SubmitButton />
</form>
)
}
import { SubmitButton } from '@/app/submit-button'
export default async function Home() {
return (
<form action={...}>
<input type="text" name="field-name" />
<SubmitButton />
</form>
)
}
Gestion des erreurs
Les Actions Serveur peuvent également retourner des objets sérialisables. Par exemple, votre Action Serveur peut gérer les erreurs lors de la création d'un nouvel élément :
'use server'
export async function createTodo(prevState: any, formData: FormData) {
try {
await createItem(formData.get('todo'))
return revalidatePath('/')
} catch (e) {
return { message: 'Failed to create' }
}
}
'use server'
export async function createTodo(prevState, formData) {
try {
await createItem(formData.get('todo'))
return revalidatePath('/')
} catch (e) {
return { message: 'Failed to create' }
}
}
Ensuite, depuis un Composant Client, vous pouvez lire cette valeur et afficher un message d'erreur.
'use client'
import { experimental_useFormState as useFormState } from 'react-dom'
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
import { createTodo } from '@/app/actions'
const initialState = {
message: null,
}
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" aria-disabled={pending}>
Ajouter
</button>
)
}
export function AddForm() {
const [state, formAction] = useFormState(createTodo, initialState)
return (
<form action={formAction}>
<label htmlFor="todo">Saisir une tâche</label>
<input type="text" id="todo" name="todo" required />
<SubmitButton />
<p aria-live="polite" className="sr-only">
{state?.message}
</p>
</form>
)
}
'use client'
import { experimental_useFormState as useFormState } from 'react-dom'
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
import { createTodo } from '@/app/actions'
const initialState = {
message: null,
}
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" aria-disabled={pending}>
Ajouter
</button>
)
}
export function AddForm() {
const [state, formAction] = useFormState(createTodo, initialState)
return (
<form action={formAction}>
<label htmlFor="todo">Saisir une tâche</label>
<input type="text" id="todo" name="todo" required />
<SubmitButton />
<p aria-live="polite" className="sr-only">
{state?.message}
</p>
</form>
)
}
Mises à jour optimistes
Utilisez useOptimistic
pour mettre à jour l'interface utilisateur de manière optimiste avant que l'Action Serveur ne se termine, plutôt que d'attendre la réponse :
'use client'
import { experimental_useOptimistic as useOptimistic } from 'react'
import { send } from './actions'
type Message = {
message: string
}
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[]>(
messages,
(state: Message[], newMessage: string) => [
...state,
{ message: newMessage },
]
)
return (
<div>
{optimisticMessages.map((m, k) => (
<div key={k}>{m.message}</div>
))}
<form
action={async (formData: FormData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}}
>
<input type="text" name="message" />
<button type="submit">Envoyer</button>
</form>
</div>
)
}
'use client'
import { experimental_useOptimistic as useOptimistic } from 'react'
import { send } from './actions'
export function Thread({ messages }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { message: newMessage }]
)
return (
<div>
{optimisticMessages.map((m) => (
<div>{m.message}</div>
))}
<form
action={async (formData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}}
>
<input type="text" name="message" />
<button type="submit">Envoyer</button>
</form>
</div>
)
}
Définition des cookies
Vous pouvez définir des cookies dans une Action Serveur en utilisant la fonction cookies
:
'use server'
import { cookies } from 'next/headers'
export async function create() {
const cart = await createCart()
cookies().set('cartId', cart.id)
}
'use server'
import { cookies } from 'next/headers'
export async function create() {
const cart = await createCart()
cookies().set('cartId', cart.id)
}
Lecture des cookies
Vous pouvez lire les cookies dans une Action Serveur en utilisant la fonction cookies
:
'use server'
import { cookies } from 'next/headers'
export async function read() {
const auth = cookies().get('authorization')?.value
// ...
}
'use server'
import { cookies } from 'next/headers'
export async function read() {
const auth = cookies().get('authorization')?.value
// ...
}
Suppression des cookies
Vous pouvez supprimer des cookies dans une Action Serveur en utilisant la fonction cookies
:
'use server'
import { cookies } from 'next/headers'
export async function delete() {
cookies().delete('name')
// ...
}
'use server'
import { cookies } from 'next/headers'
export async function delete() {
cookies().delete('name')
// ...
}
Voir des exemples supplémentaires pour supprimer des cookies depuis des Actions Serveur.