Leçon 9 / 15

Rendre la liste des dépenses

20 min React — composants et état Gratuit

Objectif : Afficher toutes les dépenses du tableau d’état dans une liste, avec une clé stable pour chaque ligne.

Quand le tableau d’état contient plusieurs dépenses, vous devez les rendre visibles sans recopier le même JSX pour chacune. Vous allez parcourir ce tableau et afficher une ligne par dépense, en reliant chaque ligne à son identifiant stable.

Le tableau `depenses` et la fonction qui l’ajoute sont déjà gérés dans `App`, comme dans les leçons précédentes. Vous allez transmettre ce tableau à `ListeDepenses`, puis transmettre chaque dépense à `LigneDepense`. Le type `Depense` reste partagé depuis `App` afin que les props des deux composants soient vérifiées par TypeScript.

Parcourir le tableau dans le composant de liste

La méthode `map` produit un élément JSX pour chaque valeur du tableau. Pour éviter d’afficher directement tous les champs dans le composant de liste, vous rendez `LigneDepense` pour chaque dépense et lui transmettez cette dépense avec la prop `depense`. Les props typées et leur transmission descendante ont été vues dans « Transmettre une dépense avec des props ».

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

type ListeDepensesProps = {
  depenses: Depense[];
};

export function ListeDepenses({ depenses }: ListeDepensesProps) {
  return (
    <section aria-label="Liste des dépenses">
      <h2>Dépenses</h2>
      <ul className="depenses-liste">
        {depenses.map((depense) => (
          <LigneDepense key={depense.id} depense={depense} />
        ))}
      </ul>
      {depenses.length === 0 ? (
        <p className="depenses-message--vide">
          Aucune dépense pour cette période.
        </p>
      ) : null}
    </section>
  );
}

L’expression dans les accolades appelle `map` et crée un composant `LigneDepense` pour chaque entrée. La prop spéciale `key` n’est pas une donnée ordinaire transmise au composant : React l’utilise pour reconnaître les éléments d’une liste lors de ses rendus. Ici, `depense.id` convient parce qu’il identifie la dépense et ne change pas lorsque la liste évolue.

Afficher le contenu d’une ligne

Le composant de ligne reçoit un seul objet et affiche ses champs dans l’ordre prévu par le squelette du projet : description, catégorie, date et montant. Il reprend les classes autorisées par le contrat, sans ajouter de classe ni de style en ligne. Le montant est formaté à l’affichage ; sa valeur stockée dans la dépense reste un nombre.

src/composants/LigneDepense.tsxTexte
import type { Depense } from "../App";

type LigneDepenseProps = {
  depense: Depense;
};

export function LigneDepense({ depense }: LigneDepenseProps) {
  const montantFormate = new Intl.NumberFormat("fr-FR", {
    style: "currency",
    currency: "EUR",
  }).format(depense.montant);

  return (
    <li className="depenses-liste__element">
      <span>{depense.description}</span>
      <span className="depenses-categorie">{depense.categorie}</span>
      <span>{depense.date}</span>
      <span>{montantFormate}</span>
    </li>
  );
}

Relier la liste à l’état du tableau de bord

Dans `App`, passez le tableau détenu par le composant parent à la liste. Quand le formulaire demande l’ajout d’une dépense, la mise à jour immuable de cet état provoque un nouveau rendu ; `ListeDepenses` reçoit alors le tableau mis à jour et `map` produit la nouvelle ligne. Le code ci-dessous montre la page complète nécessaire à cet état du projet ; les composants du formulaire, de l’entête, des filtres et du récapitulatif gardent leurs fichiers dédiés.

src/App.tsxTexte
import { useState } from "react";
import { EnteteDepenses } from "./composants/EnteteDepenses";
import { FiltresDepenses } from "./composants/FiltresDepenses";
import { FormulaireDepense } from "./composants/FormulaireDepense";
import { ListeDepenses } from "./composants/ListeDepenses";
import { RecapitulatifMensuel } from "./composants/RecapitulatifMensuel";

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

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

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

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

  return (
    <div className="depenses-app">
      <EnteteDepenses />
      <main className="depenses-principale">
        <FormulaireDepense ajouterDepense={ajouterDepense} />
        <RecapitulatifMensuel />
        <FiltresDepenses />
        <ListeDepenses depenses={depenses} />
      </main>
    </div>
  );
}

Les trois autres composants de page restent à leur état précédent : ils ne sont pas modifiés par cette leçon. Vérifiez que `FormulaireDepense` transmet bien un objet conforme au type `Depense`, notamment avec un identifiant unique. Avec la dépense de démonstration seule, la liste affiche « Courses », « Alimentation », la date et le montant formaté en euros ; après un ajout valide, une seconde ligne apparaît.

À retenir

  • `map` transforme chaque dépense du tableau en élément JSX.
  • `key` permet à React de reconnaître chaque élément de la liste.
  • Utilisez l’identifiant stable de la dépense comme valeur de `key`, pas sa position.
  • Un composant de ligne réutilisable reçoit les données nécessaires par props.
  • Le message vide s’affiche lorsque le tableau transmis ne contient aucune dépense.

Dans la prochaine leçon, vous ajouterez un filtre par catégorie sans modifier les données source affichées ici.

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.