Maîtrise l'API Context de React pour partager des données à travers ton arbre de composants sans prop drilling. Apprends createContext, Provider, useContext, et quand utiliser le contexte pour les thèmes, l'authentification et la locale.
Dans React, les données circulent vers le bas des composants parents vers les composants enfants via les props. C'est une excellente conception pour des arbres de composants simples. Mais que se passe-t-il quand une donnée doit être accessible par un composant profondément imbriqué ?
Imagine une valeur theme définie au niveau supérieur mais nécessaire par un bouton cinq niveaux plus bas :
function App() {
const theme = 'dark';
return <Layout theme={theme} />;
}
function Layout({ theme }) {
return <Sidebar theme={theme} />;
}
function Sidebar({ theme }) {
return <Menu theme={theme} />;
}
function Menu({ theme }) {
return <MenuItem theme={theme} />;
}
function MenuItem({ theme }) {
return <button className={theme}>Click me</button>;
}Chaque composant intermédiaire (Layout, Sidebar, Menu) reçoit theme uniquement pour le transmettre plus loin. Ils ne l'utilisent pas eux-mêmes. Ce motif s'appelle le prop drilling, et il crée plusieurs problèmes :
L'API Context de React résout ce problème en te permettant de partager des valeurs à travers l'arbre de composants sans passer de props à chaque niveau.
L'API Context comporte trois étapes : créer un contexte, fournir une valeur, et consommer la valeur.
const ThemeContext = React.createContext('light');createContext prend une valeur par défaut comme argument. Cette valeur par défaut est utilisée uniquement lorsqu'un composant lit le contexte mais qu'il n'y a pas de Provider correspondant au-dessus de lui dans l'arbre. Considère-la comme une solution de secours.
Enveloppe une partie de ton arbre de composants avec le composant Provider du contexte et passe-lui une prop value :
function App() {
return (
<ThemeContext.Provider value="dark">
<Layout />
</ThemeContext.Provider>
);
}Chaque composant à l'intérieur de <ThemeContext.Provider> — peu importe à quel point il est imbriqué — peut accéder à la valeur "dark". La prop value sur le Provider remplace la valeur par défaut passée à createContext.
La valeur du Provider peut être un state, rendant le contexte réactif :
function App() {
const [theme, setTheme] = React.useState('light');
return (
<ThemeContext.Provider value={theme}>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
<Layout />
</ThemeContext.Provider>
);
}Quand theme change, React re-rend tous les composants qui consomment ThemeContext.
Le hook useContext lit la valeur actuelle du Provider le plus proche au-dessus du composant dans l'arbre :
function ThemedButton() {
const theme = React.useContext(ThemeContext);
return <button className={theme}>I am {theme}!</button>;
}Peu importe à quelle profondeur ThemedButton se trouve dans l'arbre de composants, il peut lire le thème directement — aucun prop drilling requis.
Quand un composant appelle useContext(ThemeContext), React remonte vers le haut dans l'arbre de composants à la recherche du <ThemeContext.Provider> le plus proche. Il utilise la value de ce Provider. Si aucun Provider n'est trouvé, il utilise la valeur par défaut de createContext.
// Provider externe : theme = 'dark'
<ThemeContext.Provider value="dark">
<Navbar /> {/* lit 'dark' */}
{/* Provider interne : theme = 'light' */}
<ThemeContext.Provider value="light">
<Sidebar /> {/* lit 'light' — le Provider le plus proche l'emporte */}
</ThemeContext.Provider>
</ThemeContext.Provider>Il est courant d'envelopper useContext dans un hook personnalisé pour une meilleure ergonomie et une gestion d'erreur :
const ThemeContext = React.createContext(undefined);
function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function useTheme() {
const context = React.useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// Utilisation dans n'importe quel composant :
function Header() {
const { theme, setTheme } = useTheme();
return <h1 className={theme}>My App</h1>;
}Ce motif te donne une sécurité de type et un message d'erreur utile si un composant essaie d'utiliser le contexte en dehors de son Provider.
Le contexte est idéal pour des données qui sont globales ou semi-globales dans une partie de ton application. Cas d'usage courants :
Le contexte n'est pas un remplacement pour toutes les props. Évite-le quand :
C'est parfaitement acceptable (et recommandé) de diviser ton contexte par domaine plutôt que d'avoir un contexte géant :
function App() {
return (
<AuthProvider>
<ThemeProvider>
<LocaleProvider>
<Layout />
</LocaleProvider>
</ThemeProvider>
</AuthProvider>
);
}Cela garde les contextes focalisés et prévient les re-rendus inutiles. Quand le thème change, seuls les composants qui consomment ThemeContext sont re-rendus — les composants qui consomment seulement AuthContext ne sont pas affectés.
<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const ThemeContext = React.createContext("light");
function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState("light");
const toggle = () => setTheme(t => t === "light" ? "dark" : "light");
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
}
function ThemedBox() {
const { theme, toggle } = React.useContext(ThemeContext);
const style = {
background: theme === "dark" ? "#333" : "#eee",
color: theme === "dark" ? "#fff" : "#000",
padding: "20px",
borderRadius: "8px",
};
return (
<div style={style}>
<p>Current theme: {theme}</p>
<button onClick={toggle}>Toggle Theme</button>
</div>
);
}
function App() {
return (
<ThemeProvider>
<h2>Context API Demo</h2>
<ThemedBox />
</ThemeProvider>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>Que se passe-t-il quand un composant appelle useContext mais qu'il n'y a pas de Provider correspondant au-dessus de lui dans l'arbre ?