Chapitre 17 — Structurer un projet React proprement
Quand une application grandit, on se retrouve vite à passer des props de parent en enfant, puis en petit-enfant, etc. Cette situation s’appelle le props drilling (forage de props). La Context API permet de partager des données (et des fonctions) à plusieurs composants, sans devoir les transmettre à chaque niveau.
Plan du chapitre
1) Le problème : props drilling
Imaginez que App possède une donnée importante (ex : user)
et que vous voulez l’afficher dans un composant très bas dans l’arbre.
Sans Context, vous devez passer la prop à chaque niveau.
App (user)
└─ Layout (reçoit user)
└─ Page (reçoit user)
└─ ProfileCard (reçoit user)
Ce n’est pas “faux”, mais cela devient pénible et fragile : si vous oubliez une prop à un niveau, tout casse.
2) L’idée du Context
Le Context permet de créer une “boîte” globale (dans une zone de l’app) qui contient une valeur partagée. Tous les composants “dans cette zone” peuvent lire la valeur, sans qu’on doive la passer en props partout.
Concrètement, on déclare un Provider qui fournit des valeurs,
et des composants qui consomment ces valeurs avec useContext.
3) Les 3 étapes : createContext → Provider → useContext
Il y a toujours les mêmes étapes :
- Créer un context :
createContext() - Envelopper une partie de l’app avec un provider :
<XProvider> - Lire la valeur dans un composant enfant :
useContext(XContext)
import { createContext } from "react";
export const ThemeContext = createContext(null);
4) Exemple simple : thème (clair/sombre)
On va créer un context ThemeContext qui stocke :
theme: "light" ou "dark"toggleTheme(): fonction pour changer
A) Créer le context (fichier src/contexts/theme-context.jsx)
import { createContext } from "react";
export const ThemeContext = createContext(null);
B) Fournir le context (dans App.jsx)
import { useState } from "react";
import { ThemeContext } from "./contexts/theme-context.jsx";
import Layout from "./Layout.jsx";
export default function App() {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme((t) => (t === "light" ? "dark" : "light"));
};
const value = { theme, toggleTheme };
return (
<ThemeContext.Provider value={value}>
<Layout />
</ThemeContext.Provider>
);
}
C) Consommer le context (dans un composant enfant)
import { useContext } from "react";
import { ThemeContext } from "./contexts/theme-context.jsx";
export default function ThemeButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme}>
Thème actuel : {theme}
</button>
);
}
Ici, ThemeButton peut accéder à theme même si Layout ne lui passe aucune prop.
5) Exemple réaliste : utilisateur connecté
Un cas très courant : stocker l’utilisateur connecté (ou null si personne n’est connecté),
et fournir des fonctions login / logout.
A) Context “auth”
import { createContext } from "react";
export const AuthContext = createContext(null);
B) Provider (state + fonctions)
import { useState } from "react";
import { AuthContext } from "./contexts/auth-context.jsx";
export default function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (email) => {
setUser({ email });
};
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
C) Consommer
import { useContext } from "react";
import { AuthContext } from "./contexts/auth-context.jsx";
export default function UserBadge() {
const { user, logout } = useContext(AuthContext);
if (!user) return <p>Non connecté</p>;
return (
<div>
<p>Connecté : {user.email}</p>
<button onClick={logout}>Déconnexion</button>
</div>
);
}
6) Bonnes pratiques
- Ne mettez pas tout dans un seul context : créez plusieurs contexts selon les besoins.
- Le context est idéal pour : thème, langue, utilisateur, panier, préférences UI…
- Évitez d’y mettre des données qui changent en permanence (cela peut re-render beaucoup).
- Créez un dossier
contexts/pour centraliser les contexts.
7) Erreurs fréquentes
Erreur : “Cannot destructure property ... of null”
Votre useContext renvoie null car le composant n’est pas dans la zone du Provider.
Vérifiez que vous avez enveloppé l’app (ou la page) avec <XContext.Provider>.
Mon context n’est pas à jour
Vérifiez que vous mettez à jour le state via setState (immutabilité),
et que la valeur value du Provider contient bien les dernières valeurs.
Beaucoup de re-render
Si le context contient beaucoup de valeurs ou change trop souvent, beaucoup de composants re-render. On peut optimiser (mémoïsation) mais pour débuter, gardez les contexts simples.
8) Résumé (à retenir)
- Le Context évite le props drilling.
- Étapes :
createContext→Provider→useContext. - Très utile pour : thème, utilisateur, préférences, etc.
- Gardez des contexts simples et séparés.
9) Exercice pratique
Créez un context AuthContext pour gérer une connexion “fausse” (sans backend).
Étapes
- Créer
src/contexts/auth-context.jsxaveccreateContext(null). - Créer un
AuthProviderqui stockeuserdans le state. - Créer
login(email)etlogout(). - Envelopper votre app avec
AuthProvider. - Dans une page, afficher “Non connecté” ou l’email + un bouton Déconnexion.
Bonus
Ajoutez une page “Profil” protégée : si user === null, affichez un message “Accès refusé”.
Prochaine étape : Chapitre 18 — useReducer : gérer un state plus complexe (utile avant d’aller vers Redux).