Kodokon kodokon.com

Context : partager sans prop drilling

Faites circuler des données sans prop drilling grâce au contexte, en maîtrisant ses re-rendus et ses limites.

8 min · 3 questions

Ouvrir cette leçon dans Kodokon

Le prop drilling désigne ces props qui traversent trois ou quatre composants intermédiaires sans y être utilisées, juste pour atteindre une feuille de l'arbre. Chaque couche traversée se couple à une donnée qui ne la concerne pas. Le contexte règle ce problème de transport : createContext crée le canal, un provider publie une valeur, et useContext la lit depuis n'importe quel descendant. Gardez la nuance en tête : le contexte transporte de l'état, il n'en gère pas - la valeur vit toujours dans un composant, ici le provider.

JSX
import { createContext, useMemo, useState } from "react";

const ThemeContext = createContext(null);

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("dark");
  const value = useMemo(
    () => ({ theme, setTheme }),
    [theme]
  );

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}
La valeur publiée est mémoïsée pour stabiliser sa référence.

Quand la valeur du provider change, tous les consommateurs re-rendent, même profondément imbriqués - et React.memo ne les protège pas, car il compare des props, pas le contexte. Le piège classique aggrave les choses : écrire value={{ theme, setTheme }} crée un objet neuf à chaque rendu du provider, donc une nouvelle référence, donc des re-rendus en cascade même quand theme n'a pas bougé. C'est le rôle du useMemo ci-dessus. Complétez le dispositif par un hook d'accès qui échoue explicitement hors du provider.

JSX
import { useContext } from "react";

function useTheme() {
  const context = useContext(ThemeContext);
  if (context === null) {
    throw new Error(
      "useTheme must be inside <ThemeProvider>"
    );
  }
  return context;
}
Une erreur explicite vaut mieux qu'un null silencieux.

Avant de créer un contexte, épuisez la piste de la composition. Si une couche intermédiaire ne fait que relayer une prop, inversez la structure : construisez l'élément en haut de l'arbre, là où la donnée existe, et passez-le via children ou une prop dédiée. La couche intermédiaire devient générique et la donnée ne traverse plus aucun composant qui ne l'utilise pas.

JSX
function App({ currentUser }) {
  return (
    <Layout
      header={<TopBar user={currentUser} />}
      content={<Dashboard />}
    />
  );
}
Layout ignore tout de currentUser : il place des éléments prêts.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Un consommateur de contexte est enveloppé dans React.memo et la valeur du contexte change. Que se passe-t-il ?
    • Il ne re-rend pas : React.memo bloque toutes les mises à jour
    • Il re-rend : React.memo compare les props, pas les valeurs de contexte
    • Il re-rend seulement si ses props changent aussi
  2. Pourquoi mémoïser l'objet passé à value dans un provider ?
    • Pour accélérer la création de l'objet à chaque rendu
    • Parce que le provider exige une valeur immuable
    • Pour que la référence ne change que si les données changent, évitant des re-rendus inutiles des consommateurs
  3. Quelle est la limite principale du contexte pour un état mis à jour très fréquemment ?
    • Pas de sélecteur : chaque mise à jour re-rend tous les consommateurs, même ceux qui n'utilisent qu'une partie de la valeur
    • Le contexte est asynchrone et retarde les mises à jour
    • Le contexte ne peut stocker que des valeurs primitives