Le contexte : partager une donnée avec toute l'application
Une donnée utile à beaucoup de composants, sans la faire passer de props en props.
Objectifs de la séance
- reconnaître le problème du prop drilling (props transmises de niveau en niveau) ;
- créer un contexte avec
createContext, un fournisseur (provider) et un hook pour le lire ; - sécuriser le hook (erreur claire hors du fournisseur) ;
- savoir quand ne pas utiliser un contexte ;
- retirer le pseudo de l'URL.
Petite vérification
Avant de commencer, vérifiez si vous savez...
- taper
/partie/abcdans la barre d'adresse et lire le message affiché - taper
/resultat?score=9&total=3et lire le message affiché - dire quel composant de React Router affiche la page « Page introuvable »
📌 Réponse attendue
Notions théoriques
Le problème : le prop drilling
Le pseudo est saisi sur la page d'accueil. Il est utile dans l'en-tête, sur la page du résultat, et un jour sur d'autres pages. Pour l'instant, il voyage dans l'URL, ce qui pose plusieurs problèmes : il est visible et modifiable, il alourdit les adresses, et il faut le recopier à chaque navigation.
L'autre solution classique, passer le pseudo en props de parent en enfant, devient pénible quand beaucoup de niveaux séparent le composant qui possède la donnée et celui qui s'en sert. On appelle cela le prop drilling (percer les composants avec des props).
Le contexte : une donnée disponible partout dessous
Un contexte (context) rend une donnée accessible à tous les composants situés en dessous d'un point de l'arbre, sans passer par les props. Il se construit en trois morceaux :
JoueurProvider ← fournit la valeur à tout ce qui est en dessous
└── App
├── EnTete ← lit le pseudo avec useJoueur()
└── Routes
├── PageAccueil ← modifie le pseudo avec useJoueur()
└── PageResultat ← lit le pseudo avec useJoueur()
1) Créer le contexte
import { createContext } from "react";
export type JoueurContexteValeur = {
pseudo: string | null;
changerPseudo: (pseudo: string) => void;
};
export const JoueurContexte = createContext<JoueurContexteValeur | null>(null);
La valeur par défaut est null : elle signifie « aucun fournisseur au-dessus ». Le type décrit ce que les composants pourront lire : le pseudo (ou null tant qu'aucun joueur n'est connu) et une fonction pour le changer.
2) Le fournisseur
Le fournisseur (provider) est un composant qui possède l'état et le met à disposition de ses enfants.
import { useState } from "react";
import type { ReactNode } from "react";
import { JoueurContexte } from "./joueurContexte";
type JoueurProviderProps = {
children: ReactNode;
};
export function JoueurProvider({ children }: JoueurProviderProps) {
const [pseudo, setPseudo] = useState<string | null>(null);
return (
<JoueurContexte.Provider value={{ pseudo, changerPseudo: setPseudo }}>
{children}
</JoueurContexte.Provider>
);
}
On l'installe une fois, au-dessus de l'application, dans main.tsx :
<BrowserRouter>
<JoueurProvider>
<App />
</JoueurProvider>
</BrowserRouter>
3) Le hook qui lit le contexte
Plutôt que d'appeler useContext partout, on crée un hook qui vérifie qu'un fournisseur existe.
import { useContext } from "react";
import { JoueurContexte } from "../context/joueurContexte";
import type { JoueurContexteValeur } from "../context/joueurContexte";
export function useJoueur(): JoueurContexteValeur {
const contexte = useContext(JoueurContexte);
if (contexte === null) {
throw new Error("useJoueur doit être utilisé à l'intérieur d'un JoueurProvider");
}
return contexte;
}
Deux avantages :
- les composants appellent simplement
const { pseudo } = useJoueur();; - si un développeur oublie le fournisseur, il reçoit une erreur claire immédiatement, au lieu d'un
Cannot read properties of nullobscur plus tard.
Le fournisseur (un composant), le contexte (une valeur) et le hook (une fonction) sont séparés. L'outil de rechargement à chaud de Vite fonctionne mieux quand un fichier n'exporte que des composants, et npm run lint vous le signale sinon.
Quand ne PAS utiliser un contexte ?
Le contexte est pratique, mais tous les composants qui le lisent sont réaffichés quand sa valeur change. Il convient aux données :
- globales et rares à changer : le joueur connecté, le thème (clair/sombre), la langue ;
- utilisées par de nombreux composants éloignés.
Il ne convient pas à un état local (le champ du pseudo en cours de saisie) ni à une donnée qui ne traverse que deux niveaux : de simples props sont plus faciles à comprendre.
Un contexte vit en mémoire
Le pseudo du contexte disparaît quand l'utilisateur recharge la page. Pour le conserver, il faudrait le stocker dans le navigateur (localStorage) en le validant à la lecture. Ici, on affiche simplement « Joueur » si aucun pseudo n'est connu.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Votre mission : ranger le pseudo dans un contexte et le retirer des adresses.
Étape 1 — Le contexte
Créez le dossier src/context/ et le fichier joueurContexte.ts.
Une valeur par défaut null oblige à gérer l'absence de fournisseur (dans le hook). Une fausse valeur par défaut (un pseudo vide, une fonction qui ne fait rien) masquerait l'oubli du fournisseur.
Étape 2 — Le fournisseur
Créez src/context/JoueurProvider.tsx.
changerPseudo est le nom métier de l'action. Si plus tard vous devez valider ou enregistrer le pseudo, vous modifiez cette seule fonction du fournisseur, sans toucher aux composants qui l'utilisent.
Étape 3 — Le hook sécurisé
Créez src/hooks/useJoueur.ts.
Après le test, TypeScript sait que contexte n'est plus null : le type de retour est JoueurContexteValeur, sans vérification à refaire dans chaque composant.
Étape 4 — Installer le fournisseur
Dans main.tsx, enveloppez App avec JoueurProvider (à l'intérieur du BrowserRouter).
Placez le fournisseur juste au-dessus des composants qui en ont besoin. Ici, toute l'application utilise le pseudo : main.tsx est le bon endroit.
Étape 5 — Enregistrer le pseudo
Dans PageAccueil, enregistrez le pseudo dans le contexte et naviguez sans le mettre dans l'adresse.
/partie/2 est plus court, plus propre, et ne dévoile plus le pseudo. Dans PagePartie, supprimez la lecture du pseudo et retirez-le des paramètres envoyés vers /resultat.
Étape 6 — Lire le pseudo dans l'en-tête et le résultat
Adaptez EnTete et PageResultat pour lire le pseudo avec useJoueur.
lirePseudo ne sert plus : retirez-la de src/logic/parametres.ts (il ne doit rester que lireEntier). Du code mort est du code à relire pour rien.
📌 Une solution
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
| Prop drilling | Transmettre une prop à travers de nombreux niveaux : pénible à maintenir. |
| Contexte | Rend une donnée accessible à tous les composants situés sous un fournisseur. |
| Trois morceaux | createContext (le contexte), un fournisseur (qui possède l'état), un hook (qui lit). |
| Hook sécurisé | Il lève une erreur claire quand le fournisseur est absent. |
| Usage | Pour des données globales et rares à changer (joueur, thème, langue), pas pour tout. |
| Mémoire | Un contexte est perdu au rechargement de la page. |
Aperçu de la prochaine séance
Votre application est fonctionnelle et bien structurée. Dans le tutoriel 5, vous allez la tester automatiquement avec Vitest, puis la construire pour la production et comprendre ce qui change entre le développement et la mise en ligne.