Formulaires et mutations

Les formulaires vous permettent de créer et de mettre à jour des données dans les applications web. Next.js offre une manière puissante de gérer les soumissions de formulaires et les mutations de données en utilisant les routes API.

Bon à savoir :

  • Nous recommanderons bientôt d'adopter progressivement le routeur App et d'utiliser les actions serveur (Server Actions) pour gérer les soumissions de formulaires et les mutations de données. Les actions serveur vous permettent de définir des fonctions serveur asynchrones qui peuvent être appelées directement depuis vos composants, sans avoir besoin de créer manuellement une route API.
  • Les routes API ne spécifient pas d'en-têtes CORS, ce qui signifie qu'elles sont uniquement de même origine par défaut.
  • Comme les routes API s'exécutent sur le serveur, nous pouvons utiliser des valeurs sensibles (comme des clés API) via les variables d'environnement sans les exposer au client. Ceci est crucial pour la sécurité de votre application.

Exemples

Formulaire côté serveur uniquement

Avec le routeur Pages, vous devez créer manuellement des points de terminaison API pour gérer de manière sécurisée les mutations de données sur le serveur.

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

Ensuite, appelez la route API depuis le client avec un gestionnaire d'événements :

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

    // Gérer la réponse si nécessaire
    const data = await response.json()
    // ...
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit">Soumettre</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,
    })

    // Gérer la réponse si nécessaire
    const data = await response.json()
    // ...
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit">Soumettre</button>
    </form>
  )
}

Validation de formulaire

Nous recommandons d'utiliser la validation HTML comme required et type="email" pour une validation côté client de base.

Pour une validation côté serveur plus avancée, vous pouvez utiliser une bibliothèque de validation de schéma comme zod pour valider les champs du formulaire avant de muter les données :

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)
  // ...
}

Gestion des erreurs

Vous pouvez utiliser l'état React pour afficher un message d'erreur lorsqu'une soumission de formulaire échoue :

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) // Effacer les erreurs précédentes lorsqu'une nouvelle requête commence

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      if (!response.ok) {
        throw new Error('Échec de la soumission des données. Veuillez réessayer.')
      }

      // Gérer la réponse si nécessaire
      const data = await response.json()
      // ...
    } catch (error) {
      // Capturer le message d'erreur pour l'afficher à l'utilisateur
      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 ? 'Chargement...' : 'Soumettre'}
        </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) // Effacer les erreurs précédentes lorsqu'une nouvelle requête commence

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      if (!response.ok) {
        throw new Error('Échec de la soumission des données. Veuillez réessayer.')
      }

      // Gérer la réponse si nécessaire
      const data = await response.json()
      // ...
    } catch (error) {
      // Capturer le message d'erreur pour l'afficher à l'utilisateur
      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 ? 'Chargement...' : 'Soumettre'}
        </button>
      </form>
    </div>
  )
}

Affichage de l'état de chargement

Vous pouvez utiliser l'état React pour afficher un état de chargement lorsqu'un formulaire est en cours de soumission sur le serveur :

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) // Définir le chargement à true lorsque la requête commence

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      // Gérer la réponse si nécessaire
      const data = await response.json()
      // ...
    } catch (error) {
      // Gérer l'erreur si nécessaire
      console.error(error)
    } finally {
      setIsLoading(false) // Définir le chargement à false lorsque la requête se termine
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Chargement...' : 'Soumettre'}
      </button>
    </form>
  )
}
import React, { useState } from 'react'

export default function Page() {
  const [isLoading, setIsLoading] = useState(false)

  async function onSubmit(event) {
    event.preventDefault()
    setIsLoading(true) // Définir le chargement à true lorsque la requête commence

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      // Gérer la réponse si nécessaire
      const data = await response.json()
      // ...
    } catch (error) {
      // Gérer l'erreur si nécessaire
      console.error(error)
    } finally {
      setIsLoading(false) // Définir le chargement à false lorsque la requête se termine
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Chargement...' : 'Soumettre'}
      </button>
    </form>
  )
}

Redirection

Si vous souhaitez rediriger l'utilisateur vers une route différente après une mutation, vous pouvez utiliser redirect vers n'importe quelle URL absolue ou relative :

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

Définition de cookies

Vous pouvez définir des cookies dans une route API en utilisant la méthode setHeader sur la réponse :

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
  res.status(200).send('Le cookie a été défini.')
}
export default async function handler(req, res) {
  res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
  res.status(200).send('Le cookie a été défini.')
}

Lecture de cookies

Vous pouvez lire les cookies dans une route API en utilisant l'aide de requête cookies :

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const auth = req.cookies.authorization
  // ...
}
export default async function handler(req, res) {
  const auth = req.cookies.authorization
  // ...
}

Suppression de cookies

Vous pouvez supprimer des cookies dans une route API en utilisant la méthode setHeader sur la réponse :

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
  res.status(200).send('Le cookie a été supprimé.')
}
export default async function handler(req, res) {
  res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
  res.status(200).send('Le cookie a été supprimé.')
}