Leçon 8 / 15

Afficher un retour selon l’état du formulaire

12 min React — composants et état Gratuit

Objectif : Afficher un message adapté lorsque le formulaire est vide ou qu’une dépense vient d’être ajoutée.

Un formulaire peut être incomplet, en cours de saisie ou prêt à être envoyé. Sans retour visible, vous ne savez pas toujours pourquoi une dépense ne peut pas être ajoutée. Dans cette leçon, vous affichez un message adapté à la saisie et une confirmation après l’ajout, sans toucher au flux de dépenses déjà en place.

La leçon « Ajouter une dépense à la liste » a établi que le parent conserve les dépenses et que le formulaire lui transmet une dépense validée. Nous allons compléter le formulaire à partir de ses champs et de son comportement existants : le montant reste contrôlé localement, tandis que la description, la catégorie et la date sont lues lors de la soumission.

Choisir les messages selon la saisie

Un rendu conditionnel consiste à choisir le contenu JSX en fonction d’une valeur. L’opérateur ternaire suit la forme condition ? contenuSiVrai : contenuSiFaux : il est utile lorsque vous avez exactement deux affichages possibles. Ici, vous pouvez signaler que le formulaire est incomplet tant que la description, le montant, la catégorie ou la date manque.

Après un ajout valide, vous pouvez aussi présenter une confirmation. Cette information décrit une action passée, et non une donnée de dépense : un état local au formulaire est donc adapté. Il est initialisé à false, puis devient true au moment où le formulaire transmet une dépense au parent.

Mettre à jour le formulaire

Remplacez le contenu de src/composants/FormulaireDepense.tsx par le fichier complet ci-dessous. Il conserve les props, la validation, la création de l’identifiant et l’état du montant du projet ; il ajoute uniquement l’état nécessaire au message de confirmation. La confirmation est remise à zéro dès qu’une nouvelle saisie commence.

Texte
import { useState, type ChangeEvent, type FormEvent } from "react";
import type { Depense } from "../types";

type FormulaireDepenseProps = {
  ajouterDepense: (depense: Depense) => void;
};

export default function FormulaireDepense({
  ajouterDepense,
}: FormulaireDepenseProps) {
  const [montant, setMontant] = useState(0);
  const [ajoutEffectue, setAjoutEffectue] = useState(false);

  function gererChangementMontant(
    evenement: ChangeEvent<HTMLInputElement>,
  ) {
    setMontant(Number(evenement.currentTarget.value));
    setAjoutEffectue(false);
  }

  function gererSoumission(evenement: FormEvent<HTMLFormElement>) {
    evenement.preventDefault();
    const donnees = new FormData(evenement.currentTarget);
    const description = String(donnees.get("description") ?? "").trim();
    const categorie = String(donnees.get("categorie") ?? "");
    const date = String(donnees.get("date") ?? "");

    if (!description || montant <= 0 || !categorie || !date) {
      setAjoutEffectue(false);
      return;
    }

    const depense: Depense = {
      id: crypto.randomUUID(),
      description,
      montant,
      categorie,
      date,
    };

    ajouterDepense(depense);
    evenement.currentTarget.reset();
    setMontant(0);
    setAjoutEffectue(true);
  }

  return (
    <section className="depenses-saisie" aria-label="Saisir une dépense">
      <h2>Ajouter une dépense</h2>
      <form onSubmit={gererSoumission}>
        <label>
          Description
          <input
            className="depenses-saisie__champ"
            type="text"
            name="description"
            onChange={() => setAjoutEffectue(false)}
          />
        </label>
        <label>
          Montant
          <input
            className="depenses-saisie__champ"
            type="number"
            name="montant"
            value={montant === 0 ? "" : montant}
            onChange={gererChangementMontant}
          />
        </label>
        <label>
          Catégorie
          <select
            className="depenses-saisie__champ"
            name="categorie"
            defaultValue=""
            onChange={() => setAjoutEffectue(false)}
          >
            <option value="">Choisir une catégorie</option>
            <option value="Alimentation">Alimentation</option>
            <option value="Transport">Transport</option>
            <option value="Logement">Logement</option>
            <option value="Loisirs">Loisirs</option>
          </select>
        </label>
        <label>
          Date
          <input
            className="depenses-saisie__champ"
            type="date"
            name="date"
            onChange={() => setAjoutEffectue(false)}
          />
        </label>
        <button className="depenses-saisie__bouton" type="submit">
          Ajouter
        </button>
      </form>
      {ajoutEffectue ? (
        <p role="status">Dépense ajoutée.</p>
      ) : (
        <p className="depenses-message--vide">
          Complétez tous les champs pour ajouter une dépense.
        </p>
      )}
    </section>
  );
}

Le ternaire affiche un seul des deux paragraphes à la fois. Avant un ajout réussi, le formulaire indique que tous les champs doivent être complétés ; après l’ajout, il annonce la confirmation. La classe depenses-message--vide est réutilisée pour le message d’état incomplet, conformément aux classes disponibles dans le contrat du projet.

Vérifier le résultat

Au chargement, vous voyez « Complétez tous les champs pour ajouter une dépense. ». Si vous remplissez les champs et validez, le parent reçoit la dépense comme auparavant, le formulaire est réinitialisé et vous voyez « Dépense ajoutée. ». Dès que vous modifiez un champ, le message de confirmation disparaît ; une nouvelle soumission incomplète ne produit donc pas de confirmation trompeuse.

Les catégories de l’exemple reprennent des valeurs cohérentes avec le formulaire du tableau de bord ; si votre projet possède déjà une liste de catégories définie, conservez cette liste plutôt que de créer une source concurrente. La prochaine leçon affichera les dépenses enregistrées sous forme de liste avec une clé fondée sur l’identifiant de chaque dépense.

À retenir

  • Un rendu conditionnel adapte le JSX à une valeur ou à un état.
  • Le ternaire permet de choisir entre deux affichages en JSX.
  • Le message d’état vide et la confirmation ne nécessitent pas de dupliquer les dépenses.
  • Affichez la confirmation seulement après la transmission réussie de la dépense au parent.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.