Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
FrameworksIntermédiaireSérie : React / Next.js

React : Les formulaires et les inputs contrôlés

Le composant contrôlé, où React est la source de vérité de chaque champ : gérer plusieurs inputs avec un seul état, soumettre, valider pendant le rendu, et gérer cases à cocher et listes déroulantes.

Par Baptiste Vidal
4 min de lecture
Mis à jour il y a 2 semaines
reactformulairesinputscontrolledfrontend

React : Les formulaires et les inputs contrôlés

On a amorcé l'idée avec onChange : lire une saisie et la stocker dans le state. Un formulaire, c'est cette idée généralisée à plusieurs champs, avec la soumission et la validation. Tout repose sur un concept central : le composant contrôlé.


# L'input contrôlé : React, source de vérité

Un champ HTML garde normalement sa valeur tout seul, dans le DOM. En React, on préfère que ce soit le state qui pilote la valeur. On lie donc les deux :

function Champ() {
  const [nom, setNom] = useState("");
 
  return (
    <input
      value={nom}                              // le state impose la valeur
      onChange={(e) => setNom(e.target.value)} // chaque frappe met à jour le state
    />
  );
}

La boucle : je tape → onChange met à jour le state → re-rendu → value affiche le nouveau state. Le champ ne peut plus afficher autre chose que le state : c'est ça, un composant contrôlé. Avantage : la valeur est toujours disponible, prête à être validée ou envoyée.


# Plusieurs champs : un seul objet d'état

Un useState par champ devient vite lourd. On regroupe tout dans un objet, avec un seul gestionnaire générique qui s'appuie sur l'attribut name de chaque input :

function Inscription() {
  const [form, setForm] = useState({ nom: "", email: "" });
 
  function gererChangement(e) {
    const { name, value } = e.target;
    setForm((prev) => ({ ...prev, [name]: value }));
  }
 
  return (
    <form>
      <input name="nom"   value={form.nom}   onChange={gererChangement} />
      <input name="email" value={form.email} onChange={gererChangement} />
    </form>
  );
}

Deux points clés, directement hérités du state et des objets JS :

  • [name] est une clé calculée : name="email" met à jour form.email.
  • On ne mute pas l'objet : { ...prev, [name]: value } en crée une nouvelle version.

# Soumettre le formulaire

À la soumission, on bloque le rechargement de page natif avec e.preventDefault(), puis on lit le state - toutes les valeurs y sont déjà :

function gererEnvoi(e) {
  e.preventDefault();
  console.log(form); // { nom: "...", email: "..." }
  // envoi vers une API, etc.
}
 
// <form onSubmit={gererEnvoi}> ... <button>Envoyer</button> </form>

Pas besoin d'aller relire le DOM : le state est le contenu du formulaire.


# Valider... pendant le rendu

La validation d'un champ, c'est une valeur dérivée de l'état. On la calcule donc directement pendant le rendu - surtout pas dans un useEffect :

const emailValide = form.email.includes("@");
 
return (
  <form onSubmit={gererEnvoi}>
    <input name="email" value={form.email} onChange={gererChangement} />
 
    {form.email !== "" && !emailValide && (
      <p className="erreur">Email invalide</p>
    )}
 
    <button disabled={!emailValide}>Envoyer</button>
  </form>
);

emailValide est recalculé à chaque rendu, donc toujours à jour. Le message d'erreur et l'état du bouton en découlent par rendu conditionnel.


# Cases à cocher et listes déroulantes

Le principe « valeur pilotée par le state » reste le même, seule la propriété change :

// Case à cocher : on lit e.target.checked (un booléen)
<input
  type="checkbox"
  checked={accepteCGU}
  onChange={(e) => setAccepteCGU(e.target.checked)}
/>
 
// Liste déroulante : value sur le <select>
<select value={pays} onChange={(e) => setPays(e.target.value)}>
  <option value="fr">France</option>
  <option value="be">Belgique</option>
</select>

Retiens : value pour du texte et les select, checked pour les cases.


# Nuance : contrôlé vs non contrôlé

À l'inverse du composant contrôlé, un composant non contrôlé laisse le DOM gérer la valeur ; on la relit au besoin via une référence (useRef) et on fixe une valeur initiale avec defaultValue. C'est plus léger pour un cas simple, et obligatoire pour un <input type="file"> (dont la valeur ne peut pas être imposée par React).

En pratique : le contrôlé est le choix par défaut (validation en direct, valeur toujours accessible) ; le non contrôlé reste une exception ponctuelle.


# À retenir

  • Un input contrôlé lie value au state et le met à jour via onChange : React est la source de vérité.
  • Plusieurs champs → un objet d'état + un gestionnaire générique basé sur name (clé calculée, spread, jamais de mutation).
  • À la soumission : e.preventDefault(), puis on lit directement le state.
  • La validation est une valeur dérivée : on la calcule pendant le rendu, pas dans un useEffect.
  • checked pour les cases, value pour le texte et les select.

Prochaine étape : quand plusieurs composants doivent partager le même état, on le fait remonter vers leur parent - la communication entre composants (lifting state up), puis le Context.