Chapitre 4 : useContext
Le problème : Prop Drilling
Quand des données doivent passer à travers plusieurs niveaux de composants :
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
import { createContext } from 'react';
const UserContext = createContext(null);
Étape 2 : Fournir la valeur (Provider)
function App() {
const [user, setUser] = useState({ name: 'Jean' });
return (
<UserContext.Provider value={user}>
<Layout />
</UserContext.Provider>
);
}
Étape 3 : Consommer la valeur (useContext)
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 :
function Layout() {
return (
<div>
<Header />
<Main />
</div>
);
}
function Header() {
return <Navbar />;
}
function Navbar() {
return <UserMenu />;
}
Exemple complet : Thème
// 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;
}
// App.js
import { ThemeProvider } from './ThemeContext';
function App() {
return (
<ThemeProvider>
<Layout />
</ThemeProvider>
);
}
// ThemeToggle.js
import { useTheme } from './ThemeContext';
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Mode actuel : {theme}
</button>
);
}
// 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 :
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 :
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 :
// 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);
}
// 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é ->