Réduire le couplage avec un reducer
Objectif : Centraliser les transitions des filtres et du mois sélectionné dans des reducers typés, sans déplacer ces états hors de leur portée prévue.
Quand plusieurs interactions modifient un même état, des appels de mise à jour dispersés rendent les transitions difficiles à suivre. Un reducer rassemble ces règles dans une fonction explicite : il reçoit l’état courant et une action, puis retourne le nouvel état. Vous allez appliquer ce mécanisme aux filtres locaux et à la préférence de mois, sans confondre leurs portées.
La leçon « Garder les interactions propres à leur composant » a établi que les filtres restent locaux, tandis que « Partager les préférences avec un contexte ciblé » a placé le mois dans son contexte. Vous conservez ces décisions : chaque état garde son propriétaire. Le reducer organise les changements à l’intérieur de ces frontières ; il ne constitue pas, à lui seul, un motif pour déplacer toutes les données dans un état global.
Décrire les transitions avec des actions
Une action nomme un événement et transporte les données dont la transition a besoin. Dans le tableau de dépenses, choisir une catégorie et saisir une recherche sont deux événements distincts ; sélectionner un mois en est un autre. Les unions TypeScript rendent les actions limitées et vérifiables : une action de recherche porte du texte, tandis qu’une action de catégorie porte un identifiant ou la valeur « toutes ».
type CategorieSelectionnee = "toutes" | string;
type Filtres = {
categorie: CategorieSelectionnee;
recherche: string;
};
type ActionFiltre =
| { type: "categorie/selectionnee"; categorie: CategorieSelectionnee }
| { type: "recherche/modifiee"; recherche: string }
| { type: "filtres/reinitialises" };
function reduireFiltres(etat: Filtres, action: ActionFiltre): Filtres {
switch (action.type) {
case "categorie/selectionnee":
return { ...etat, categorie: action.categorie };
case "recherche/modifiee":
return { ...etat, recherche: action.recherche };
case "filtres/reinitialises":
return { categorie: "toutes", recherche: "" };
}
}
type MoisSelectionne = { mois: string };
type ActionMois = { type: "mois/selectionne"; mois: string };
function reduireMois(etat: MoisSelectionne, action: ActionMois): MoisSelectionne {
switch (action.type) {
case "mois/selectionne":
return { mois: action.mois };
}
}
const filtresInitiaux: Filtres = { categorie: "toutes", recherche: "" };
const moisInitial: MoisSelectionne = { mois: "2025-03" };
const filtresApresRecherche = reduireFiltres(filtresInitiaux, {
type: "recherche/modifiee",
recherche: "transport",
});
const filtresReinitialises = reduireFiltres(filtresApresRecherche, {
type: "filtres/reinitialises",
});
const moisApresSelection = reduireMois(moisInitial, {
type: "mois/selectionne",
mois: "2025-04",
});
console.log(filtresApresRecherche, filtresReinitialises, moisApresSelection);{ categorie: "toutes", recherche: "transport" } { categorie: "toutes", recherche: "" } { mois: "2025-04" }Ce fichier TypeScript est autonome : vous pouvez l’enregistrer sous un nom temporaire en .ts et l’exécuter avec votre environnement TypeScript. Chaque fonction est pure : elle ne modifie pas l’objet reçu, ne lit pas le DOM et retourne un nouvel état. Le spread conserve l’autre filtre lorsqu’une seule propriété change ; la réinitialisation, elle, remet les deux valeurs à leur état initial.
Brancher le reducer sans élargir la portée
Dans FiltresDepenses, utilisez useReducer avec reduireFiltres et filtresInitiaux à la place de deux états indépendants. Le composant envoie ensuite une action depuis le gestionnaire de changement de catégorie ou de recherche, puis calcule les dépenses affichées à partir des filtres obtenus. Le reducer ne contient pas la liste des dépenses : il ne gère que les valeurs de filtre, conformément au contrat du projet.
Pour le mois, utilisez le reducer dans le contexte qui expose déjà cette préférence. Le composant de récapitulatif continue à lire le mois et à déclencher sa sélection par le hook prévu ; le contexte distribue la valeur et l’action, sans intégrer les filtres locaux. Si l’implémentation du contexte expose actuellement une fonction de mise à jour, adaptez cette fonction pour envoyer l’action au reducer tout en conservant la même interface utilisée par le récapitulatif.
Quand préférer un reducer
Un reducer est pertinent lorsque plusieurs actions modifient un ensemble cohérent de valeurs, ou lorsque les règles de transition méritent d’être nommées et testées. Pour un état simple avec une seule mise à jour évidente, useState reste souvent plus lisible. N’ajoutez pas un reducer aux dépenses uniquement pour homogénéiser le code : leur ajout et leur suppression devront être centralisés si les transitions deviennent assez nombreuses ou liées entre elles.
À retenir
- Un reducer reçoit un état et une action typée, puis retourne le nouvel état.
- Les filtres restent locaux à FiltresDepenses ; le mois reste dans son contexte.
- Une transition ne modifie pas l’état reçu : elle produit une nouvelle valeur.
- Choisissez useReducer quand ses règles explicites rendent les changements plus simples à comprendre et à tester.
Dans la prochaine leçon, vous examinerez dans quels cas une bibliothèque de gestion d’état apporte réellement un avantage au tableau de bord.