React Context API et performance
React & Performance

Common React Context Errors: Performance Guide and Best Practices

Comment éviter les re-renders en cascade, le God Context et les patterns anti-performance. Guide pragmatique et actionnable.

🇫🇷 This article is currently available in French. English translation in progress.

Le Context API de React est puissant pour partager des informations dans une application. Mais mal employé, il peut provoquer des re-renders en cascade, dégrader les performances et rendre le code difficile à faire évoluer. Voici un guide pragmatique et actionnable pour éviter les pièges les plus fréquents.

Pourquoi le contexte re-render "tout" si souvent

  • Un Provider re-render dès que sa valeur change, et tous les consommateurs descendants re-render aussi.
  • Passer des objets/fonctions non mémoïsés crée de nouvelles références à chaque render.
  • Mélanger "état qui bouge tout le temps" et "API stable" dans un même contexte amplifie l'effet domino.

💡 Point clé : Le Context API ne fait PAS de "smart re-rendering". Dès que la valeur du Provider change (référence différente), React re-render tous les composants qui consomment ce contexte, même s'ils n'utilisent qu'une fraction de l'état.

Les erreurs les plus fréquentes

❌ 1. Le "God Context" (contexte fourre-tout)

Regrouper auth, thème, préférences, data, notifications dans un seul contexte monolithique.

Conséquence : Chaque changement (même mineur) re-render TOUS les consommateurs, même ceux qui n'utilisent qu'une fraction de l'état.

❌ 2. Stocker des valeurs à haute fréquence

Inputs contrôlés, données temps réel (websockets), timers dans le contexte.

Conséquence : Des dizaines/centaines de re-renders par seconde sur toute l'arborescence.

❌ 3. useContext partout par défaut

Ajouter useContext dans chaque composant au lieu de découper et rapprocher l'état du composant qui l'utilise.

Conséquence : Surface de re-render maximale, couplage fort, difficile à tester et refactorer.

❌ 4. Oublier la stabilité des références

Valeurs d'un Provider non mémoïsées (objets/fonctions recréés à chaque render).

// ❌ Mauvais : nouvelle référence à chaque render
<MyContext.Provider value={{ user, setUser }}>

// ✅ Bon : référence stable
const value = useMemo(() => ({ user, setUser }), [user, setUser])

❌ 5. Coupler lecture et écriture dans un seul contexte

State + mutations dans le même contexte obligent à propager des mises à jour inutiles.

Conséquence : Composants qui n'affichent que des fonctions (actions) re-render quand l'état change.

❌ 6. Dériver des objets complexes dans le Provider

Créer des objets complexes { a, b, c, helper(a,b) } à chaque render du Provider.

Conséquence : Calculs coûteux répétés, références instables, re-renders en cascade.

Les bonnes pratiques essentielles

✅ 1. Créer plusieurs contextes spécialisés

Exemple : AuthContext, ThemeContext, SettingsContext, PermissionsContext.

Bénéfice : On isole les surfaces de re-render. Un changement de thème ne re-render pas les composants qui consomment uniquement l'auth.

✅ 2. Séparer API (mutations) et State (muable)

Contexte "API" (fonctions) stable : ne change jamais ou très rarement.
Contexte "State" pour la partie qui varie réellement.

Résultat : On peut utiliser les APIs sans causer de re-render.

// Context A : API (stable)
const AuthAPI = createContext({ login, logout, refresh })

// Context B : State (muable)
const AuthState = createContext({ user, isLoading })

✅ 3. Mémoïser la valeur du Provider

Stabiliser objets et fonctions (mémoïsation, références stables). N'exposer que le nécessaire.

const value = useMemo(
  () => ({ user, setUser }),
  [user, setUser]
)
<MyContext.Provider value={value}>

✅ 4. Préférer les props quand c'est local

Si l'information est consommée par quelques composants proches, pas besoin de contexte. Un simple passage de props est plus lisible et performant.

✅ 5. Co-localiser l'état

Garder l'état au plus près du composant qui le consomme. Ne "monter" l'état que si indispensable.

Règle d'or : État local par défaut, contexte uniquement si nécessaire.

✅ 6. Segmenter la consommation

Créer des hooks sélectifs (lecture d'un seul fragment). Éviter de consommer la valeur globale.

// ❌ Mauvais : consomme tout
const { user, theme, settings } = useAppContext()

// ✅ Bon : hook sélectif
const user = useUser()
const theme = useTheme()

Patterns recommandés (simples et efficaces)

🔵Pattern 1 : Dual Contexts (lecture/écriture séparées)

Context A : API (fonctions stables, ne re-render pas).
Context B : State (données mutables).

Les composants qui déclenchent des actions importent l'API ; ceux qui affichent, le State.

// authContext.tsx
const AuthAPIContext = createContext()
const AuthStateContext = createContext()

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const [isLoading, setIsLoading] = useState(false)

  // API stable (ne change jamais)
  const api = useMemo(() => ({
    login: async (credentials) => { /* ... */ },
    logout: () => { /* ... */ },
    refresh: () => { /* ... */ }
  }), [])

  // State muable
  const state = useMemo(() => ({
    user,
    isLoading
  }), [user, isLoading])

  return (
    <AuthAPIContext.Provider value={api}>
      <AuthStateContext.Provider value={state}>
        {children}
      </AuthStateContext.Provider>
    </AuthAPIContext.Provider>
  )
}

// Hooks sélectifs
export const useAuthAPI = () => useContext(AuthAPIContext)
export const useAuthState = () => useContext(AuthStateContext)

// Usage
// Composant qui affiche : useAuthState() → re-render quand user change
// Composant qui agit : useAuthAPI() → ne re-render JAMAIS

🟣Pattern 2 : Provider minimal

Exposer des scalaires/objets stables, pas des objets reconstruits à la volée. Extraire les calculs dérivés hors de la valeur du Provider.

// ❌ Mauvais : calculs dérivés dans Provider
const value = {
  user,
  isAdmin: user?.role === 'admin', // recalculé à chaque render
  fullName: `${user?.firstName} ${user?.lastName}`
}

// ✅ Bon : exposer uniquement les données brutes
const value = useMemo(() => ({ user }), [user])

// Calculs dérivés dans custom hook ou composant
const useIsAdmin = () => {
  const { user } = useAuthState()
  return user?.role === 'admin'
}

🟠Pattern 3 : External store pour le très dynamique

Pour du state global très mouvant (sockets, formulaires complexes), envisager useSyncExternalStore ou un store externe (Zustand, Redux Toolkit) pour contrôler finement les re-renders.

Bénéfice : Selectors granulaires, performance optimale pour les données temps réel.

✅ Check-list anti re-render

Le contexte contient-il uniquement des valeurs réellement "globales" et stables ?
Les fonctions exposées sont-elles stables dans le temps (références inchangées) ?
Ai-je séparé "API" et "State" en deux contextes distincts ?
Puis-je remplacer un usage de contexte par une prop locale ?
Ai-je évité d'inclure des valeurs haute-fréquence (saisie, horloges) ?
Les composants consommateurs lisent-ils uniquement le strict nécessaire ?

🔄 Stratégie de migration progressive

1

Identifier le "God context" et les re-renders indésirables

Utiliser React DevTools Profiler pour mesurer les re-renders et identifier les hotspots.

2

Découper en contextes spécialisés par domaine fonctionnel

Créer AuthContext, ThemeContext, SettingsContext, etc.

3

Scinder API (fonctions) et State (muable)

Implémenter le pattern Dual Context pour chaque domaine.

4

Stabiliser la valeur des Providers

Ajouter useMemo/useCallback pour stabiliser les références.

5

Ramener l'état au plus près des consommateurs

Descendre l'état local là où il est réellement consommé.

6

Mesurer à nouveau et itérer

Re-profiler avec React DevTools et comparer les résultats avant/après.

⚠️ Quand ne PAS utiliser le contexte

  • Données locales ou éphémères (compteur, toggle, formulaire simple).
  • Données extrêmement volatiles (temps réel, inputs) sans besoin de diffusion globale.
  • Cas où un simple passage de props est plus lisible et plus performant (composants proches).

Règle d'or : État local par défaut. Contexte uniquement si plusieurs composants distants dans l'arborescence ont besoin de partager une information stable.

🎯 Résultat attendu

Performance

Moins de re-renders, meilleure réactivité UI, temps de réponse réduits, Core Web Vitals optimisés.

🧩

Maintenabilité

Code prévisible, domaines bien séparés, tests facilités, refactoring simplifié.

📈

Scalabilité

Architecture prête pour la montée en charge (features, équipes, perfs).

Et vous, plutôt "un seul contexte pour tout" ou "context minimaliste" ?

Choisissez la clarté : plusieurs contextes spécialisés, API stables, state ciblé. Votre UI — et vos Core Web Vitals — vous diront merci.

📚 Articles connexes