Chapitre 4 : useContext
Le problème : Prop Drilling
Quand des données doivent passer à travers plusieurs niveaux de composants :
- snippet.javascript
function App() { const [user, setUser] = useState({ name: 'Jean' }); return <Layout user={user} />; } function Layout({ user }) { return ( <div> <Header user={user} /> <Main user={user} /> </div> ); } function Header({ user }) { return <Navbar user={user} />; } function Navbar({ user }) { return <UserMenu user={user} />; // Enfin utilisé ici ! } function UserMenu({ user }) { return <span>{user.name}</span>; }
Layout et Header ne font que transmettre user sans l'utiliser. C'est le prop drilling.
La solution : Context
Context permet de “téléporter” des données à travers l'arbre de composants.
Étape 1 : Créer le Context
- snippet.javascript
import { createContext } from 'react'; const UserContext = createContext(null);
Étape 2 : Fournir la valeur (Provider)
- snippet.javascript
function App() { const [user, setUser] = useState({ name: 'Jean' }); return ( <UserContext.Provider value={user}> <Layout /> </UserContext.Provider> ); }
Étape 3 : Consommer la valeur (useContext)
- snippet.javascript
import { useContext } from 'react'; function UserMenu() { const user = useContext(UserContext); return <span>{user.name}</span>; }
Les composants intermédiaires n'ont plus besoin de passer user :
- snippet.javascript
function Layout() { return ( <div> <Header /> <Main /> </div> ); } function Header() { return <Navbar />; } function Navbar() { return <UserMenu />; }
Exemple complet : Thème
- snippet.javascript
// ThemeContext.js import { createContext, useContext, useState } from 'react'; const ThemeContext = createContext(null); export function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; }
- snippet.javascript
// App.js import { ThemeProvider } from './ThemeContext'; function App() { return ( <ThemeProvider> <Layout /> </ThemeProvider> ); }
- snippet.javascript
// ThemeToggle.js import { useTheme } from './ThemeContext'; function ThemeToggle() { const { theme, toggleTheme } = useTheme(); return ( <button onClick={toggleTheme}> Mode actuel : {theme} </button> ); }
- snippet.javascript
// Card.js import { useTheme } from './ThemeContext'; function Card({ children }) { const { theme } = useTheme(); return ( <div className={`card card-${theme}`}> {children} </div> ); }
Passer des fonctions via Context
Vous pouvez passer des fonctions pour permettre aux enfants de modifier l'état :
- snippet.javascript
const AuthContext = createContext(null); function AuthProvider({ children }) { const [user, setUser] = useState(null); const login = async (credentials) => { const response = await api.login(credentials); setUser(response.user); }; const logout = () => { setUser(null); }; return ( <AuthContext.Provider value={{ user, login, logout }}> {children} </AuthContext.Provider> ); } function useAuth() { return useContext(AuthContext); } // Utilisation function LoginButton() { const { user, login, logout } = useAuth(); if (user) { return <button onClick={logout}>Déconnexion</button>; } return <button onClick={() => login({ email, password })}>Connexion</button>; }
Quand utiliser Context ?
Bons cas d'usage :
- Thème (dark/light mode)
- Utilisateur connecté
- Langue / internationalisation
- Configuration globale
Mauvais cas d'usage :
- État local d'un formulaire
- Données qui ne concernent que quelques composants proches
- État qui change très fréquemment (performance)
Context vs Props
| Critère | Props | Context |
|---|---|---|
| Données concernent peu de composants | ✅ | ❌ |
| Données traversent beaucoup de niveaux | ❌ | ✅ |
| Flux de données explicite | ✅ | ❌ |
| Configuration globale | ❌ | ✅ |
Plusieurs Contexts
Vous pouvez imbriquer plusieurs Providers :
- snippet.javascript
function App() { return ( <AuthProvider> <ThemeProvider> <LanguageProvider> <Layout /> </LanguageProvider> </ThemeProvider> </AuthProvider> ); }
Exercices
Exercice 1 : Context de langue
Créer un context pour gérer la langue (fr/en) avec un bouton pour changer.
Solution :
- snippet.javascript
// LanguageContext.js import { createContext, useContext, useState } from 'react'; const LanguageContext = createContext(null); const translations = { fr: { greeting: 'Bonjour', button: 'Anglais' }, en: { greeting: 'Hello', button: 'French' } }; export function LanguageProvider({ children }) { const [lang, setLang] = useState('fr'); const toggleLang = () => { setLang(prev => prev === 'fr' ? 'en' : 'fr'); }; const t = (key) => translations[lang][key]; return ( <LanguageContext.Provider value={{ lang, toggleLang, t }}> {children} </LanguageContext.Provider> ); } export function useLanguage() { return useContext(LanguageContext); }
- snippet.javascript
// Greeting.js function Greeting() { const { t, toggleLang } = useLanguage(); return ( <div> <h1>{t('greeting')}</h1> <button onClick={toggleLang}>{t('button')}</button> </div> ); }
Points clés à retenir
- Context évite le prop drilling
- createContext → Provider → useContext
- Créer un custom hook (useTheme, useAuth) pour simplifier l'usage
- Utiliser pour des données vraiment globales
- Ne pas abuser : les props restent souvent plus claires
Récapitulatif du Module 3
| Hook | Usage | Déclenche un re-rendu |
|---|---|---|
| useState | État local | Oui |
| useEffect | Effets de bord (API, events) | Non (mais peut modifier l'état) |
| useRef | DOM et valeurs persistantes | Non |
| useContext | Données globales | Oui (si la valeur change) |
← Chapitre précédent | Retour au module | Module suivant : React Avancé →