Partager les préférences avec un contexte ciblé
Objectif : Exposer le mois sélectionné via un contexte sans y placer les données qui n’ont pas besoin d’être globales.
Le récapitulatif mensuel doit connaître le mois affiché. Si plusieurs zones de l’application doivent aussi lire ou modifier ce choix, transmettre la valeur de composant en composant devient une chaîne de props difficile à maintenir. Vous allez partager uniquement cette préférence avec un contexte, sans y déplacer les dépenses ni les filtres de la liste.
Les leçons précédentes ont établi que chaque donnée a une source de vérité et que les interactions propres à un composant restent locales. Ici, le mois sélectionné est partagé parce qu’il peut être utilisé par plusieurs zones ; la recherche et le filtre de catégorie restent locaux au composant qui affiche les dépenses. Le contexte ne sert donc pas à rendre global tout état de l’application.
Créer un contexte à portée limitée
Le contrat du projet réserve src/contexts/DepensesContext.tsx au contexte des dépenses et des catégories. Pour ne pas mélanger les responsabilités, le code ci-dessous ajoute dans ce même fichier un contexte distinct pour la préférence du mois. La valeur exposée comprend le mois courant et une fonction de mise à jour ; les composants consommateurs n’ont pas besoin de connaître la manière dont cette valeur est conservée.
import { createContext, useContext, useState } from "react";
import type { Dispatch, ReactNode, SetStateAction } from "react";
interface MoisSelectionne {
mois: string;
definirMois: Dispatch<SetStateAction<string>>;
}
const MoisContext = createContext<MoisSelectionne | null>(null);
interface FournisseurMoisProps {
children: ReactNode;
}
export function FournisseurMois({ children }: FournisseurMoisProps) {
const [mois, definirMois] = useState(() => {
const maintenant = new Date();
const annee = maintenant.getFullYear();
const moisNumerique = String(maintenant.getMonth() + 1).padStart(2, "0");
return `${annee}-${moisNumerique}`;
});
return (
<MoisContext.Provider value={{ mois, definirMois }}>
{children}
</MoisContext.Provider>
);
}
export function useMoisSelectionne(): MoisSelectionne {
const contexte = useContext(MoisContext);
if (contexte === null) {
throw new Error("useMoisSelectionne doit être utilisé dans FournisseurMois.");
}
return contexte;
}Le type de la valeur du contexte est explicite et le contexte accepte temporairement null pour signaler qu’aucun fournisseur ne l’entoure. Le hook useMoisSelectionne centralise cette vérification : un composant consommateur reçoit une valeur utilisable, ou une erreur claire indique que le fournisseur manque. Le mois initial respecte le format attendu par un champ HTML type="month", soit AAAA-MM.
Placer le fournisseur autour des zones concernées
Le fournisseur doit englober les composants qui utilisent le mois, mais pas nécessairement toute l’application. Dans le tableau de bord, le placer autour du contenu principal rend la préférence accessible au récapitulatif et aux futurs consommateurs de cette sélection. L’entête et la navigation restent en dehors, puisqu’elles n’en ont pas besoin.
import { FournisseurMois } from "./contexts/DepensesContext";
import { Entete } from "./components/Entete";
import { Navigation } from "./components/Navigation";
import { Recapitulatif } from "./components/Recapitulatif";
import { FormulaireDepense } from "./components/FormulaireDepense";
import { FiltresDepenses } from "./components/FiltresDepenses";
import { ListeDepenses } from "./components/ListeDepenses";
export function App() {
return (
<div className="atelier-app">
<Entete />
<Navigation />
<main className="atelier-principal">
<FournisseurMois>
<Recapitulatif />
</FournisseurMois>
<FormulaireDepense onAjouter={() => {}} />
<FiltresDepenses />
<ListeDepenses />
</main>
</div>
);
}Ce placement illustre la portée du contexte : seule la zone récapitulative est incluse pour l’instant. Si une autre zone doit réellement utiliser le mois, élargissez le fournisseur jusqu’à son ancêtre commun avec le récapitulatif. Ne le remontez pas par anticipation. Dans le projet complet, les autres composants et leurs props conservent leur branchement existant ; le fournisseur ne remplace ni le contexte des dépenses ni la logique d’ajout déjà préparée.
Consommer la préférence dans le récapitulatif
Le consommateur lit la valeur avec le hook dédié et relie directement le champ du mois à celle-ci. Le contexte fournit l’état partagé ; il ne calcule pas le total mensuel. Le calcul du récapitulatif reste dans le hook métier prévu à cet effet, qui recevra le mois sélectionné et les dépenses selon le branchement du projet.
import { useMoisSelectionne } from "../contexts/DepensesContext";
export function Recapitulatif() {
const { mois, definirMois } = useMoisSelectionne();
return (
<section
id="recapitulatif"
className="atelier-recapitulatif"
aria-labelledby="titre-recapitulatif"
>
<h2 id="titre-recapitulatif">Récapitulatif mensuel</h2>
<label htmlFor="mois">Mois</label>
<input
id="mois"
name="mois"
type="month"
value={mois}
onChange={(evenement) => definirMois(evenement.currentTarget.value)}
/>
<p>Total du mois : <strong className="atelier-recapitulatif__montant">0,00 €</strong></p>
<p>Nombre de dépenses : 0</p>
<ul aria-label="Totaux par catégorie" />
</section>
);
}Dans cet exemple, le total et le nombre sont des valeurs provisoires pour isoler le fonctionnement du contexte ; raccordez-les au hook de récapitulatif du projet plutôt que de créer un second état. La même règle s’applique au mois : il n’est stocké qu’une fois, dans le fournisseur. Le contexte ne constitue pas une solution universelle au partage d’état ; la prochaine leçon examinera une autre manière de structurer les mises à jour lorsque plusieurs opérations doivent être coordonnées.
À retenir
- Utilisez un contexte pour une valeur réellement partagée, pas pour centraliser tous les états.
- Exposez le mois sélectionné et sa mise à jour dans une valeur de contexte typée.
- Placez le fournisseur au plus près de l’ancêtre commun des consommateurs concernés.
- Conservez les dépenses dans leur contexte dédié et les filtres dans leurs composants.