Leçon 7 / 15

Ajouter une dépense à la liste

25 min React — composants et état Gratuit

Objectif : Ajouter une dépense validée à l’état sans modifier directement le tableau existant.

Quand vous validez le formulaire, les valeurs saisies doivent rejoindre la liste des dépenses. Le formulaire ne doit pas modifier lui-même cette liste : il transmet la nouvelle dépense au composant qui détient les données.

Dans cette leçon, vous reliez la soumission du formulaire à l’état des dépenses détenu par App. Vous verrez aussi comment ajouter une dépense sans modifier le tableau déjà en mémoire.

Faire remonter la nouvelle dépense

L’état des dépenses appartient au composant parent, ici App, qui peut ensuite le transmettre aux composants chargés de l’afficher. Le formulaire reçoit une prop onAjouter : c’est une fonction que le parent fournit pour recevoir une dépense validée. Vous connaissez déjà la gestion de useState et du champ contrôlé ; reportez-vous aux deux leçons précédentes pour ces notions.

Le type Depense ci-dessous reprend la structure définie au début du cours et lui ajoute un identifiant unique, nécessaire pour reconnaître chaque entrée. L’exemple initialise la liste avec une dépense de démonstration, puis transmet la fonction d’ajout au formulaire. Si votre App contient déjà une dépense de démonstration, gardez ses valeurs dans l’initialisation de depenses.

Texte
import { useState } from "react";
import FormulaireDepense from "./composants/FormulaireDepense";

export type Depense = {
  id: string;
  description: string;
  montant: number;
  categorie: string;
  date: string;
};

const depenseInitiale: Depense = {
  id: "depense-demonstration",
  description: "Courses",
  montant: 42.5,
  categorie: "Alimentation",
  date: "2025-03-12",
};

export default function App() {
  const [depenses, setDepenses] = useState<Depense[]>([depenseInitiale]);

  function ajouterDepense(nouvelleDepense: Depense) {
    setDepenses((depensesActuelles) => [...depensesActuelles, nouvelleDepense]);
  }

  return (
    <div className="depenses-app">
      <header className="depenses-entete">
        <h1 className="depenses-entete__titre">Tableau de bord des dépenses</h1>
      </header>
      <main className="depenses-principale">
        <FormulaireDepense onAjouter={ajouterDepense} />
        <p>Nombre de dépenses enregistrées : {depenses.length}</p>
      </main>
    </div>
  );
}

La fonction de mise à jour reçoit la valeur précédente de l’état et construit un nouveau tableau avec l’opérateur de décomposition. Elle conserve les dépenses déjà présentes et ajoute la nouvelle à la fin. Ne faites pas depenses.push(nouvelleDepense) : cette opération modifierait le tableau existant au lieu d’en produire un nouveau.

Valider et transmettre les données du formulaire

Le gestionnaire soumettre intercepte l’événement de soumission et appelle preventDefault() pour empêcher le rechargement de la page. Il lit ensuite les champs, vérifie qu’ils sont renseignés et que le montant est valide, puis construit un objet conforme au type Depense. Le champ montant reste contrôlé par son état local, comme dans la leçon précédente.

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

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

const categories = ["Alimentation", "Transport", "Logement", "Loisirs"];

export default function FormulaireDepense({ onAjouter }: FormulaireDepenseProps) {
  const [montant, setMontant] = useState<number>(0);

  function soumettre(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 || !categorie || !date || montant <= 0) return;

    onAjouter({
      id: crypto.randomUUID(),
      description,
      montant,
      categorie,
      date,
    });
    evenement.currentTarget.reset();
    setMontant(0);
  }

  return (
    <section className="depenses-saisie" aria-label="Saisir une dépense">
      <h2>Ajouter une dépense</h2>
      <form onSubmit={soumettre}>
        <label>
          Description
          <input className="depenses-saisie__champ" type="text" name="description" />
        </label>
        <label>
          Montant
          <input
            className="depenses-saisie__champ"
            type="number"
            name="montant"
            min="0"
            step="0.01"
            value={montant === 0 ? "" : montant}
            onChange={(evenement) => setMontant(Number(evenement.currentTarget.value))}
          />
        </label>
        <label>
          Catégorie
          <select className="depenses-saisie__champ" name="categorie" defaultValue="">
            <option value="">Choisir une catégorie</option>
            {categories.map((categorie) => (
              <option key={categorie} value={categorie}>{categorie}</option>
            ))}
          </select>
        </label>
        <label>
          Date
          <input className="depenses-saisie__champ" type="date" name="date" />
        </label>
        <button className="depenses-saisie__bouton" type="submit">Ajouter</button>
      </form>
    </section>
  );
}

Après validation, onAjouter transmet l’objet à App et le nombre affiché augmente. Le formulaire réinitialise les champs et remet le montant à zéro. Ici, le nombre sert uniquement à vérifier l’ajout ; la liste complète sera rendue dans une leçon ultérieure, avec des clés fondées sur l’identifiant.

À retenir

  • Le formulaire intercepte la soumission et empêche le rechargement de la page.
  • Les champs sont vérifiés avant de construire une dépense valide.
  • Le parent détient la liste et fournit au formulaire une fonction de transmission.
  • Ajoutez avec un nouveau tableau, par exemple `[...depensesActuelles, nouvelleDepense]`, sans modifier l’ancien.
  • Un identifiant unique est créé une fois, lors de la création de la dépense.

Dans la prochaine leçon, vous afficherez un retour adapté lorsque le formulaire ne contient pas encore de saisie valide.

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.