Introduction/Guides/Formulaires

Comment créer des formulaires avec les Server Actions

Les Server Actions de React sont des fonctions serveur qui s'exécutent côté serveur. Elles peuvent être appelées dans les composants Serveur et Client pour gérer les soumissions de formulaires. Ce guide vous montrera comment créer des formulaires dans Next.js avec les Server Actions.

Fonctionnement

React étend l'élément HTML <form> pour permettre l'invocation des Server Actions via l'attribut action.

Lorsqu'elle est utilisée dans un formulaire, la fonction reçoit automatiquement l'objet FormData. Vous pouvez ensuite extraire les données en utilisant les méthodes natives de FormData :

export default function Page() {
  async function createInvoice(formData: FormData) {
    'use server'

    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }

    // muter les données
    // revalider le cache
  }

  return <form action={createInvoice}>...</form>
}

Bon à savoir : Pour les formulaires avec plusieurs champs, vous pouvez utiliser la méthode entries() avec Object.fromEntries() de JavaScript. Par exemple : const rawFormData = Object.fromEntries(formData).

Passage d'arguments supplémentaires

En dehors des champs de formulaire, vous pouvez passer des arguments supplémentaires à une fonction serveur en utilisant la méthode JavaScript bind. Par exemple, pour passer l'argument userId à la fonction serveur updateUser :

'use client'

import { updateUser } from './actions'

export function UserProfile({ userId }: { userId: string }) {
  const updateUserWithId = updateUser.bind(null, userId)

  return (
    <form action={updateUserWithId}>
      <input type="text" name="name" />
      <button type="submit">Mettre à jour le nom</button>
    </form>
  )
}

La fonction serveur recevra userId comme argument supplémentaire :

'use server'

export async function updateUser(userId: string, formData: FormData) {}

Bon à savoir :

  • Une alternative consiste à passer les arguments sous forme de champs cachés dans le formulaire (par exemple <input type="hidden" name="userId" value={userId} />). Cependant, la valeur fera partie du HTML rendu et ne sera pas encodée.
  • bind fonctionne dans les composants Serveur et Client et prend en charge l'amélioration progressive.

Validation des formulaires

Les formulaires peuvent être validés côté client ou serveur.

  • Pour la validation côté client, vous pouvez utiliser les attributs HTML comme required et type="email" pour une validation basique.
  • Pour la validation côté serveur, vous pouvez utiliser une bibliothèque comme zod pour valider les champs du formulaire. Par exemple :
'use server'

import { z } from 'zod'

const schema = z.object({
  email: z.string({
    invalid_type_error: 'Email invalide',
  }),
})

export default async function createUser(formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
  })

  // Retour anticipé si les données du formulaire sont invalides
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }

  // Muter les données
}

Erreurs de validation

Pour afficher les erreurs ou messages de validation, transformez le composant qui définit le <form> en composant Client et utilisez useActionState de React.

Avec useActionState, la signature de la fonction serveur changera pour recevoir un nouveau paramètre prevState ou initialState comme premier argument.

'use server'

import { z } from 'zod'

export async function createUser(initialState: any, formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
  })
  // ...
}
'use server'

import { z } from 'zod'

// ...

export async function createUser(initialState, formData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
  })
  // ...
}

Vous pouvez ensuite afficher conditionnellement le message d'erreur en fonction de l'objet state.

'use client'

import { useActionState } from 'react'
import { createUser } from '@/app/actions'

const initialState = {
  message: '',
}

export function Signup() {
  const [state, formAction, pending] = useActionState(createUser, initialState)

  return (
    <form action={formAction}>
      <label htmlFor="email">Email</label>
      <input type="text" id="email" name="email" required />
      {/* ... */}
      <p aria-live="polite">{state?.message}</p>
      <button disabled={pending}>S'inscrire</button>
    </form>
  )
}

États d'attente

Le hook useActionState expose un booléen pending qui peut être utilisé pour afficher un indicateur de chargement ou désactiver le bouton de soumission pendant l'exécution de l'action.

'use client'

import { useActionState } from 'react'
import { createUser } from '@/app/actions'

export function Signup() {
  const [state, formAction, pending] = useActionState(createUser, initialState)

  return (
    <form action={formAction}>
      {/* Autres éléments du formulaire */}
      <button disabled={pending}>S'inscrire</button>
    </form>
  )
}

Alternativement, vous pouvez utiliser le hook useFormStatus pour afficher un indicateur de chargement pendant l'exécution de l'action. Avec ce hook, vous devrez créer un composant séparé pour afficher l'indicateur. Par exemple, pour désactiver le bouton pendant l'attente :

'use client'

import { useFormStatus } from 'react-dom'

export function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button disabled={pending} type="submit">
      S'inscrire
    </button>
  )
}

Vous pouvez ensuite imbriquer le composant SubmitButton dans le formulaire :

import { SubmitButton } from './button'
import { createUser } from '@/app/actions'

export function Signup() {
  return (
    <form action={createUser}>
      {/* Autres éléments du formulaire */}
      <SubmitButton />
    </form>
  )
}

Bon à savoir : Dans React 19, useFormStatus inclut des clés supplémentaires sur l'objet retourné, comme data, method et action. Si vous n'utilisez pas React 19, seule la clé pending est disponible.

Mises à jour optimistes

Vous pouvez utiliser le hook React useOptimistic pour mettre à jour l'interface de manière optimiste avant que la fonction serveur ne termine son exécution, plutôt que d'attendre la réponse :

'use client'

import { useOptimistic } from 'react'
import { send } from './actions'

type Message = {
  message: string
}

export function Thread({ messages }: { messages: Message[] }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic<
    Message[],
    string
  >(messages, (state, newMessage) => [...state, { message: newMessage }])

  const formAction = async (formData: FormData) => {
    const message = formData.get('message') as string
    addOptimisticMessage(message)
    await send(message)
  }

  return (
    <div>
      {optimisticMessages.map((m, i) => (
        <div key={i}>{m.message}</div>
      ))}
      <form action={formAction}>
        <input type="text" name="message" />
        <button type="submit">Envoyer</button>
      </form>
    </div>
  )
}

Éléments de formulaire imbriqués

Vous pouvez appeler des Server Actions dans des éléments imbriqués à l'intérieur de <form> comme <button>, <input type="submit"> et <input type="image">. Ces éléments acceptent la prop formAction ou des gestionnaires d'événements.

C'est utile lorsque vous souhaitez appeler plusieurs Server Actions dans un formulaire. Par exemple, vous pouvez créer un élément <button> spécifique pour enregistrer un brouillon de publication en plus de le publier. Voir la documentation React sur <form> pour plus d'informations.

Soumission programmatique de formulaire

Vous pouvez déclencher une soumission de formulaire programmatiquement en utilisant la méthode requestSubmit(). Par exemple, lorsque l'utilisateur soumet un formulaire avec le raccourci clavier + Entrée, vous pouvez écouter l'événement onKeyDown :

'use client'

export function Entry() {
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (
      (e.ctrlKey || e.metaKey) &&
      (e.key === 'Enter' || e.key === 'NumpadEnter')
    ) {
      e.preventDefault()
      e.currentTarget.form?.requestSubmit()
    }
  }

  return (
    <div>
      <textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
    </div>
  )
}

Cela déclenchera la soumission du <form> ancêtre le plus proche, ce qui invoquera la fonction serveur.

On this page