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
🔄 Stratégie de migration progressive
Identifier le "God context" et les re-renders indésirables
Utiliser React DevTools Profiler pour mesurer les re-renders et identifier les hotspots.
Découper en contextes spécialisés par domaine fonctionnel
Créer AuthContext, ThemeContext, SettingsContext, etc.
Scinder API (fonctions) et State (muable)
Implémenter le pattern Dual Context pour chaque domaine.
Stabiliser la valeur des Providers
Ajouter useMemo/useCallback pour stabiliser les références.
Ramener l'état au plus près des consommateurs
Descendre l'état local là où il est réellement consommé.
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.