Leçon 10 / 15

Remonter l’état partagé

20 min React — données, effets et formulaires Gratuit

Objectif : Placer la liste des dépenses dans un composant parent afin de la partager entre le formulaire et la liste.

Le formulaire ajoute des dépenses et la liste les affiche. Si chacun de ces composants conserve sa propre copie des dépenses, ils peuvent rapidement présenter des données différentes. Vous allez placer cet état dans `App`, qui pourra le transmettre aux deux composants et recevoir les nouvelles dépenses du formulaire.

Dans la leçon précédente, vous avez déjà transmis une dépense validée à son parent et ajouté cette dépense avec une mise à jour fonctionnelle. Ici, vous formalisez ce flux : le parent détient la liste, la transmet aux composants qui en ont besoin, et fournit au formulaire un moyen de demander une mise à jour.

Détenir l’état partagé dans App

Une donnée doit être conservée par le composant le plus proche qui en a besoin et qui peut la partager. Le formulaire a besoin de signaler une nouvelle dépense ; la liste a besoin de recevoir toutes les dépenses à afficher. Comme `App` contient ces deux composants, l’état des dépenses y est placé.

src/App.tsxTexte
import { useState } from "react";
import FormulaireDepense from "./composants/FormulaireDepense";
import ListeDepenses from "./composants/ListeDepenses";
import type { Depense } from "./types";

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

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

  return (
    <div className="depenses">
      <header className="depenses__entete">
        <h1>Tableau de bord des dépenses</h1>
        <p>Suivez vos dépenses personnelles, vos catégories et votre budget mensuel.</p>
      </header>

      <main className="depenses__contenu">
        <section className="depenses__panneau" aria-labelledby="titre-saisie">
          <h2 className="depenses__titre" id="titre-saisie">Ajouter une dépense</h2>
          <FormulaireDepense onAjouter={ajouterDepense} />
        </section>

        <section className="depenses__panneau" aria-labelledby="titre-filtres">
          <h2 className="depenses__titre" id="titre-filtres">Filtres</h2>
          <div className="depenses__filtres">
            <label htmlFor="filtre-mois">Mois</label>
            <input id="filtre-mois" name="mois" type="month" />
            <label htmlFor="filtre-categorie">Catégorie</label>
            <select id="filtre-categorie" name="categorie">
              <option value="">Toutes les catégories</option>
            </select>
          </div>
        </section>

        <section className="depenses__panneau" aria-labelledby="titre-liste">
          <h2 className="depenses__titre" id="titre-liste">Dépenses</h2>
          <div className="depenses__etat" role="status" aria-live="polite"></div>
          <ListeDepenses depenses={depenses} />
        </section>

        <section className="depenses__panneau" aria-labelledby="titre-recapitulatif">
          <h2 className="depenses__titre" id="titre-recapitulatif">Récapitulatif mensuel</h2>
          <div className="depenses__recapitulatif">
            <p>Mois sélectionné : <span></span></p>
            <p>Total des dépenses : <strong>0,00 €</strong></p>
            <p>Nombre de dépenses : <strong>0</strong></p>
          </div>
        </section>
      </main>
    </div>
  );
}

`depenses` est désormais l’état canonique de la liste dans `App`. La fonction `ajouterDepense` applique une mise à jour fonctionnelle : elle construit un nouveau tableau à partir de l’ancien et de la dépense reçue, sans modifier le tableau existant. Le formulaire reçoit cette fonction sous la prop `onAjouter` ; la liste reçoit les données sous la prop `depenses`.

Déclarer les props des composants

Une prop est une valeur transmise par un composant parent à un composant enfant. Dans `ListeDepenses`, les dépenses sont uniquement lues et affichées. Le composant ne crée pas une seconde liste dans son propre état : il utilise directement la prop reçue.

src/composants/ListeDepenses.tsxTexte
import type { Depense } from "../types";

interface ListeDepensesProps {
  depenses: Depense[];
}

export default function ListeDepenses({ depenses }: ListeDepensesProps) {
  return (
    <ul className="depenses__liste">
      {depenses.map((depense) => (
        <li className="depenses__ligne" key={depense.id}>
          <span>{depense.libelle}</span>
          <span>{depense.categorie}</span>
          <time dateTime={depense.date}>{depense.date}</time>
          <span>{depense.montant.toFixed(2)} €</span>
        </li>
      ))}
    </ul>
  );
}

Le formulaire reçoit une prop fonctionnelle. Après sa validation, il appelle `onAjouter` avec l’objet qu’il vient de construire ; il ne modifie pas lui-même la liste. Le type de la fonction indique explicitement qu’elle reçoit une dépense et ne renvoie pas de valeur.

Contrat des props de src/composants/FormulaireDepense.tsxTexte
import type { Depense } from "../types";

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

export default function FormulaireDepense({ onAjouter }: FormulaireDepenseProps) {
  // Conservez ici les champs contrôlés et la validation déjà construits.
  // Après validation, transmettez l’objet créé ainsi :
  // onAjouter(nouvelleDepense);
  return null;
}

Suivre le trajet d’une dépense

Lorsqu’une personne valide le formulaire, `FormulaireDepense` appelle `onAjouter`. Cette fonction, définie dans `App`, met à jour l’état ; React réaffiche alors `App` et transmet la nouvelle liste à `ListeDepenses`. Les données descendent par les props, tandis que la demande de modification remonte par l’appel de la fonction fournie par le parent.

Ce flux ne crée pas de copie locale des dépenses dans les enfants. Le formulaire conserve seulement les valeurs temporaires de ses champs, tandis que `App` détient la collection partagée. Les filtres et le récapitulatif restent inchangés dans cette étape ; leur calcul à partir des dépenses sera abordé dans la leçon suivante.

À retenir

  • Placez l’état partagé dans le parent commun des composants concernés, ici `App`.
  • Transmettez les données aux enfants avec des props typées.
  • Transmettez une fonction de rappel au formulaire pour demander une mise à jour au parent.
  • Laissez la liste afficher la prop reçue, sans conserver une copie locale des dépenses.

Dans la prochaine leçon, vous calculerez la liste filtrée et le récapitulatif à partir des dépenses et des filtres.

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.