Avancé30 min de lecture

Context API

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.

Le problème du Prop Drilling

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 :

jsx
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 :

  • Code verbeux : Chaque composant dans la chaîne doit déclarer et transférer la prop.
  • Couplage fort : Les composants intermédiaires deviennent dépendants de props qu'ils n'utilisent pas.
  • Refactorisation pénible : Ajouter ou renommer une prop partagée signifie mettre à jour chaque composant dans la chaîne.
  • Problèmes d'évolutivité : Au fur et à mesure que ton application grandit, tu finis par transmettre de nombreuses props à travers de nombreux niveaux.

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.

createContext et Provider

L'API Context comporte trois étapes : créer un contexte, fournir une valeur, et consommer la valeur.

Étape 1 : Créer un contexte

jsx
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.

Étape 2 : Fournir une valeur

Enveloppe une partie de ton arbre de composants avec le composant Provider du contexte et passe-lui une prop value :

jsx
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.

Valeurs dynamiques

La valeur du Provider peut être un state, rendant le contexte réactif :

jsx
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.

useContext : Consommer les valeurs de contexte

Étape 3 : Consommer la valeur avec useContext

Le hook useContext lit la valeur actuelle du Provider le plus proche au-dessus du composant dans l'arbre :

jsx
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.

Comment React trouve la valeur

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.

jsx
// 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>

Un motif courant : Contexte + Hook personnalisé

Il est courant d'envelopper useContext dans un hook personnalisé pour une meilleure ergonomie et une gestion d'erreur :

jsx
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.

Quand utiliser le contexte

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 :

  • Thème (mode clair/sombre) : Presque chaque composant peut avoir besoin de connaître le thème actuel.
  • Authentification : L'objet utilisateur actuel et les fonctions de connexion/déconnexion.
  • Locale / i18n : La langue actuelle et la fonction de traduction.
  • Feature flags : Quelles fonctionnalités sont activées pour l'utilisateur actuel.
  • État d'interface : Barre latérale ouverte/fermée, notifications toast.

Quand NE PAS utiliser le contexte

Le contexte n'est pas un remplacement pour toutes les props. Évite-le quand :

  • Les données ne sont nécessaires que sur un ou deux niveaux. Passe simplement des props. Le contexte ajoute de la complexité.
  • Les données changent très fréquemment (par exemple, position de la souris à 60fps). Chaque mise à jour du contexte re-rend tous les consommateurs. Pour des mises à jour à haute fréquence, considère une ref ou une bibliothèque de gestion d'état.
  • Tu veux un contrôle fin. Le contexte n'a pas de sélecteurs — quand la valeur du contexte change, tous les consommateurs sont re-rendus, même s'ils n'utilisent qu'une partie de la valeur. Des bibliothèques comme Zustand ou Jotai offrent des abonnements à des morceaux spécifiques d'état.

Contextes multiples

C'est parfaitement acceptable (et recommandé) de diviser ton contexte par domaine plutôt que d'avoir un contexte géant :

jsx
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.

Context API en action

html
<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 ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.