Aller au contenu principal

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...

  1. taper /partie/abc dans la barre d'adresse et lire le message affiché
  2. taper /resultat?score=9&total=3 et lire le message affiché
  3. 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​

src/context/joueurContexte.ts
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.

src/context/JoueurProvider.tsx
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 :

src/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.

src/hooks/useJoueur.ts
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 :

  1. les composants appellent simplement const { pseudo } = useJoueur(); ;
  2. si un développeur oublie le fournisseur, il reçoit une erreur claire immédiatement, au lieu d'un Cannot read properties of null obscur plus tard.
Pourquoi trois fichiers ?

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​


À quoi sert un contexte React ?


Quel composant fournit la valeur du contexte à ses enfants ?


Pourquoi le hook useJoueur lève-t-il une erreur quand le contexte vaut null ?


Qu'arrive-t-il au pseudo stocké dans le contexte quand la page est rechargée ?


Dans quel cas un simple passage de props est-il préférable à un contexte ?


Que se passe-t-il quand la valeur du contexte change ?



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.


Bonne pratique - null comme valeur par défaut

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.


Bonne pratique - Exposer une fonction, pas le setter brut

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.


Bonne pratique - Échouer tôt et clairement

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).


Bonne pratique - Le fournisseur le plus bas possible

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.


Bonne pratique - Des adresses plus simples

/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.


Bonne pratique - Supprimer le code devenu inutile

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​

NotionRésumé
Prop drillingTransmettre une prop à travers de nombreux niveaux : pénible à maintenir.
ContexteRend une donnée accessible à tous les composants situés sous un fournisseur.
Trois morceauxcreateContext (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.
UsagePour des données globales et rares à changer (joueur, thème, langue), pas pour tout.
MémoireUn 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.