Assembler les composants et leurs responsabilités
Objectif : Relier le chargement, le formulaire, la liste, les filtres et le récapitulatif dans une structure cohérente.
Un tableau de bord peut afficher correctement chaque panneau tout en restant incohérent : le formulaire ajoute une dépense dans une liste, tandis que la liste chargée par le hook reste ailleurs. Vous allez réunir ces responsabilités dans un arbre de composants lisible et faire transiter les données par des props. Ainsi, l’ajout, les filtres et le récapitulatif travaillent sur les mêmes dépenses.
Une responsabilité par niveau
`App` orchestre la page : il détient la liste canonique des dépenses et les valeurs des filtres, puis les transmet aux composants concernés. `useDepenses` s’occupe du chargement et signale son état, sans conserver une seconde copie des dépenses. Le formulaire gère la saisie et appelle une fonction reçue en prop ; les filtres signalent leurs changements ; la liste et le récapitulatif affichent les données calculées par `App`.
Vous avez déjà défini les props et la remontée d’état dans la leçon « Remonter l’état partagé », et les résultats filtrés restent dérivés comme dans « Calculer les filtres et le récapitulatif ». L’assemblage final consiste à respecter ces décisions : un composant reçoit les données dont il a besoin et ne recrée pas un état concurrent pour les mêmes informations.
Corriger la jonction entre le hook et App
Un état initialisé une seule fois à partir d’un résultat qui arrive plus tard ne se synchronise pas automatiquement avec ce résultat. Pour éviter ce piège et conserver les ajouts dans la même liste, faites écrire le résultat du chargement dans l’état détenu par `App`. Le hook conserve la responsabilité du chargement, de l’erreur et du nettoyage ; il reçoit le rappel qui transmet les données chargées au parent.
import { useMemo, useState } from "react";
import Entete from "./composants/Entete";
import FormulaireDepense from "./composants/FormulaireDepense";
import FiltresDepenses from "./composants/FiltresDepenses";
import ListeDepenses from "./composants/ListeDepenses";
import RecapitulatifMensuel from "./composants/RecapitulatifMensuel";
import { useDepenses } from "./hooks/useDepenses";
import type { Depense } from "./types";
export default function App() {
const [depenses, setDepenses] = useState<Depense[]>([]);
const [mois, setMois] = useState("");
const [categorie, setCategorie] = useState("");
const { chargement, erreur } = useDepenses(setDepenses);
const depensesDuMois = useMemo(
() => depenses.filter((depense) => !mois || depense.date.startsWith(mois)),
[depenses, mois]
);
const depensesFiltrees = useMemo(
() => depensesDuMois.filter((depense) => !categorie || depense.categorie === categorie),
[depensesDuMois, categorie]
);
function ajouterDepense(nouvelleDepense: Depense) {
setDepenses((actuelles) => [...actuelles, nouvelleDepense]);
}
return (
<div className="depenses">
<Entete />
<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>
<FiltresDepenses mois={mois} categorie={categorie} onMoisChange={setMois} onCategorieChange={setCategorie} />
</section>
<section className="depenses__panneau" aria-labelledby="titre-liste">
<h2 className="depenses__titre" id="titre-liste">Dépenses</h2>
<ListeDepenses depenses={depensesFiltrees} chargement={chargement} erreur={erreur} />
</section>
<section className="depenses__panneau" aria-labelledby="titre-recapitulatif">
<h2 className="depenses__titre" id="titre-recapitulatif">Récapitulatif mensuel</h2>
<RecapitulatifMensuel depenses={depensesDuMois} mois={mois} />
</section>
</main>
</div>
);
}Cet assemblage suppose que les composants utilisent les props déjà définies dans votre projet : `FormulaireDepense` appelle `onAjouter` après validation, `FiltresDepenses` appelle les deux rappels de changement, et `ListeDepenses` reçoit les trois informations nécessaires à son affichage. Le code reprend les noms du contrat et les champs `date` et `categorie` du modèle de dépense. Gardez les interfaces de props correspondantes dans les fichiers concernés ; ne changez pas les noms de fichiers ni les classes autorisées.
Vérifier le sens des données
La liste reçoit les dépenses filtrées par mois et catégorie. Le récapitulatif reçoit seulement les dépenses du mois : il ne doit pas perdre une dépense parce que l’utilisateur a sélectionné une catégorie dans le filtre de la liste. Cette distinction préserve le comportement établi précédemment. Les filtres et les totaux sont recalculés depuis les données et les critères courants, sans nouvel état React dédié.
Quand une dépense est ajoutée, le rappel met à jour la liste canonique de manière immuable. React réaffiche alors les composants qui en dépendent : la liste filtrée et le récapitulatif reçoivent leurs nouvelles valeurs. Quand le chargement échoue, `ListeDepenses` reçoit l’erreur et peut afficher l’état prévu par le contrat, sans que le formulaire ou le récapitulatif aient à gérer la requête.
À retenir
- `App` orchestre les composants et détient les dépenses ainsi que les filtres.
- Le hook charge les données et transmet le résultat à l’état d’`App`, sans garder une copie concurrente.
- Le formulaire et les filtres communiquent leurs changements par des rappels transmis en props.
- La liste utilise les dépenses filtrées ; le récapitulatif mensuel ignore le filtre de catégorie.
Dans la prochaine leçon, vous vérifierez les parcours de saisie et de consultation, y compris les états limites.