🔗 Le problème : Prop Drilling
Le prop drilling consiste à passer des props à travers plusieurs niveaux de composants intermédiaires qui n'en ont pas besoin, uniquement pour les transmettre à un descendant profond.
// ❌ Prop drilling — theme est passé à chaque niveau
function App() {
const [theme, setTheme] = useState('dark');
return <Page theme={theme} setTheme={setTheme} />;
}
function Page({ theme, setTheme }) {
return <Header theme={theme} setTheme={setTheme} />;
}
function Header({ theme, setTheme }) {
return <Navbar theme={theme} setTheme={setTheme} />;
}
function Navbar({ theme, setTheme }) {
return <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>Toggle</button>;
}
⚠️ Context ne résout pas tout — pour les props directes parent→enfant, les props restent préférables. Réservez Context pour les données vraiment globales : thème, auth, langue.
🏗️ Créer un Context
import { createContext, useState, useContext } from 'react';
// 1. Créer le context avec une valeur par défaut
const ThemeContext = createContext('dark');
// 2. Créer un Provider personnalisé (bonne pratique)
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('dark');
function toggleTheme() {
setTheme(t => t === 'dark' ? 'light' : 'dark');
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Hook personnalisé pour consommer le context
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme doit être dans ThemeProvider');
return context;
}
🎁 Envelopper l'app avec le Provider
// index.jsx — on enveloppe l'app
import { ThemeProvider } from './ThemeContext';
function App() {
return (
<ThemeProvider>
<Page />
</ThemeProvider>
);
}
// Page n'a plus besoin de transmettre theme !
function Page() {
return <Header />;
}
function Header() {
return <Navbar />;
}
🎣 useContext — consommer le context
// N'importe quel descendant peut accéder au context
function Navbar() {
const { theme, toggleTheme } = useTheme(); // hook personnalisé
return (
<nav style={{
background: theme === 'dark' ? '#0a0e1a' : '#f0f6fc',
color: theme === 'dark' ? '#e6edf3' : '#1c1c1c'
}}>
<button onClick={toggleTheme}>
{theme === 'dark' ? '☀️' : '🌙'}
</button>
</nav>
);
}
// Composant profondément imbriqué — toujours accès direct
function BoutonTheme() {
const { theme, toggleTheme } = useTheme();
return <button onClick={toggleTheme}>Mode {theme}</button>;
}
🔧 Context Auth — pattern courant
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
async function login(email, password) {
const userData = await fakeLogin(email, password);
setUser(userData);
}
function logout() {
setUser(null);
}
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}
// Utilisation — n'importe où dans l'arbre
function ProfilPage() {
const { user, logout } = useAuth();
if (!user) return <p>Non connecté</p>;
return <div>{user.nom} <button onClick={logout}>Déconnexion</button></div>;
}