Utilisez React Context pour un environnement partagé, et non comme un tiroir à ordures.
Le contexte distribue des valeurs à travers un arbre ; il ne définit pas la propriété, ne met pas à jour les limites ni un modèle d’état. Préférez des fournisseurs spécialisés et des outils réels gérant l’état du serveur.
Sources: Most Developers Use React Context for the Wrong Problems
La transmission de propriétés n’est pas toujours une faute d’architecture
React Context est souvent utilisé comme solution rapide. Une valeur traverse trois composants qui ne l’utilisent jamais. Un autre écran a besoin de l’utilisateur actuel. Une boîte modale doit s’ouvrir depuis un niveau profond de l’arbre. Transmettre les propriétés devient fastidieux, alors un fournisseur apparaît et l’arbre semble soudainement plus ordonné.
Les composants intermédiaires éliminent les propriétés inutilisées. Les enfants situés en profondeur obtiennent ce dont ils ont besoin via un hook. La structure semble plus intentionnelle que de faire passer le même argument à chaque niveau.
Cette amélioration esthétique peut masquer une question plus importante : cette valeur devrait-elle vraiment se trouver dans Context ?
Le contexte résout un problème : un parent peut publier une valeur auprès de ses descendants sans avoir à la transmettre à travers chaque fichier intermédiaire. Il ne décide pas où l’état doit être stocké, avec quelle fréquence les consommateurs doivent se mettre à jour, si des valeurs non liées doivent partager un même conteneur, ou quel composant est responsable d’un flux de travail.
Ces décisions restent en vigueur. Le contexte peut les cacher, car l’accès aux données devient invisible. Un enfant peut lire des données sans avoir à les déclarer dans ses props. Un fournisseur peut accumuler des champs non liés, car tout semble « global ». Une petite mise à jour peut atteindre des dizaines de consommateurs qui partagent un même objet contextuel. Un widget censé être réutilisable peut dépendre d’un fournisseur qui n’existe que sur une seule page.
La faiblesse ne réside pas dans l’API. La faiblesse vient du fait d’utiliser un accès plus simple comme substitut à une gestion plus claire des responsabilités.
Les props présentent une mauvaise réputation en raison de la répétition visible dans leur parcours. Une valeur commence élevée, traverse les composants de l’interface, pour finalement atteindre le composant qui en a besoin. Les couches intermédiaires ressemblent à des systèmes de plomberie, ce qui fait que Context semble être une amélioration.
Parfois, cette répétition contient des informations utiles : elle indique l’origine de la valeur, quelles branches en dépendent et jusqu’où s’étend cette dépendance. Les props permettent de rechercher les entrées facilement. Lorsque la valeur change ou disparaît, l’impact reste local et évident.
Context efface ce parcours. Un descendant peut commencer à être utilisé sans avoir à mettre à jour les contrats intermédiaires. L’arborescence dépend toujours du fournisseur, mais cette relation n’apparaît plus sur l’interface du composant.
Cela ne signifie pas que chaque chaîne longue de props doit être conservée. Lorsqu’un grand sous-arbre partage réellement une seule préoccupation, l’utilisation de threads devient inutile. L’erreur consiste à considérer chaque prop répété comme une preuve qu’un contexte manque.
Parfois, la valeur est trop élevée en raison d’une mauvaise attribution des propriétés. Une page contient un brouillon de dialogue et le transmet à travers plusieurs niveaux jusqu’à un formulaire. Le contexte faciliterait l’accès, mais une conception plus solide pourrait permettre au formulaire ou au dialogue de gérer directement le brouillon.
Parfois, la composition élimine l’étape intermédiaire. Un parent peut créer l’élément qui a besoin des données et le transmettre en tant que children à un layout. Le layout n’apprend jamais rien au sujet de cette dépendance.
Les props répétés ne sont qu’un symptôme. Le contexte n’est qu’un remède parmi d’autres, et ce n’est pas le diagnostic par défaut.
Le contexte ne transforme pas de manière sûre l’état local en état global
Une pratique courante consiste à inventer un contexte chaque fois que plusieurs composants doivent mettre à jour le même état. Le fournisseur stocke les valeurs, exporte des fonctions de mise à jour, et chaque composant descendant peut lire ou écrire.
Cela commence souvent de manière modeste :
const AppContext = createContext(null);
function AppProvider({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [activeModal, setActiveModal] = useState(null);
const [selectedRecord, setSelectedRecord] = useState(null);
return (
<AppContext.Provider
value={{
sidebarOpen,
setSidebarOpen,
activeModal,
setActiveModal,
selectedRecord,
setSelectedRecord,
}}
>
{children}
</AppContext.Provider>
);
}
L’accès se trouve en un seul endroit, mais la signification de l’état reste floue. La visibilité d’une barre latérale, le modeleur qui est ouvert, ainsi que le record sélectionné appartiennent souvent à des fonctionnalités distinctes et évoluent selon des calendriers différents.
Chaque composant enfant dans l’arborescence peut alors réécrire directement le stockage du fournisseur. Une ligne de tableau peut définir une sélection, ouvrir un modeleur et fermer la barre latérale. Les composants n’expriment pas d’intention ; ils accèdent aux champs partagés et composent la combinaison dont ils ont besoin.
Un couplage invisible s’ensuit. Le modal suppose qu’une sélection existe déjà. Le tableau part du principe que fermer le modal efface la sélection. La barre latérale réagit à un flux de travail qui ne lui appartient pas. Ces relations ne sont pas capturées dans un réducteur, une opération ou une frontière de fonctionnalité — elles existent uniquement parce que de nombreux composants peuvent appeler de nombreux setters.
Le fait de placer des champs dans le Contexte ne fait que déplacer leur stockage ; la clarté du sens n’apparaît pas automatiquement.
Un fournisseur plus solide expose des opérations plutôt que des setters bruts, et ce uniquement pour une problématique cohérente. Un système de dialogues pourrait offrir openDialog et closeDialog. Un fournisseur d’authentification pourrait exposer des données de session et des opérations de connexion. Un fournisseur de thème pourrait exposer le thème actuel ainsi qu’un mécanisme d’actualisation dédié.
Ne vous arrêtez pas à l’idée que « plusieurs composants ont besoin d’accès ». Demandez-vous s’ils partagent un même cycle de vie et une même problématique.
Un objet de contexte peut créer une surface d’actualisation très vaste
Les problèmes surviennent lorsque un fournisseur regroupe des valeurs ayant des fréquences d’actualisation incompatibles.
function ApplicationProvider({ children }) {
const value = {
user,
theme,
locale,
notifications,
sidebarOpen,
featureFlags,
liveJobStatus,
};
return (
<ApplicationContext.Provider value={value}>
{children}
</ApplicationContext.Provider>
);
}
Les équipes ne remarquent parfois ce coût qu’après avoir effectué un profilage. À ce stade, de nombreuses fonctionnalités ont déjà intégré ce mécanisme complexe. Pour le séparer, il faut identifier tous les consommateurs et déterminer quelles parties ils ont réellement besoin.
Préférez des fournisseurs étroits alignés sur le rythme de mise à jour. Placez les éléments graphiques à forte rotation dans un endroit et les données de session stables dans un autre. Mémorisez les valeurs des fournisseurs lorsque l’identité de l’objet changerait sinon à chaque rendu. Mesurez les effets avant d’ajouter « juste un champ de plus » à un ensemble existant.
Le contexte peut masquer l’absence de limites entre composants
Lorsqu’il est difficile de connecter des fonctionnalités, le contexte peut dissimuler l’absence de frontières claires. Un éditeur de produit qui lit silencieusement l’état du produit actif, des versions en draft, des validations, des permissions et des mutations à partir d’un contexte au niveau de la page n’a de sens que dans cette page précise.
Certaines dépendances sont naturellement implicites car elles décrivent l’environnement plutôt que les données fonctionnelles — thème, localisation, routage, un client HTTP partagé. Ce type de dépendances est généralement large et relativement stable.
Les données de fonctionnalité sont différentes. Les cacher dans Context élimine le bruit des props tout en augmentant la dépendance architecturale. L’absence de props ne signifie pas l’absence de dépendances ; cela indique souvent que ces dernières sont hors de vue.
Demandez-vous si le composant peut être compris et testé en dehors du provider qui l’entoure actuellement. Si la réponse est non, Context pourrait compenser un manque de clarté dans les contrats de props ou de composition.
Context est souvent utilisé à la place d’une gestion adéquate de l’état serveur
Une autre mauvaise utilisation consiste à remplir Context de données récupérées parce que plusieurs composants en ont besoin. Le provider effectue une requête une seule fois, stocke la réponse et expose les données, les états de chargement, les erreurs ainsi que la fonction de mise à jour. Cela ressemble à une couche de données partagée sans avoir recours à une autre bibliothèque.
L’état réel d’un serveur nécessite rapidement davantage : suppression des requêtes redondantes, règles de mise à jour conditionnée par l’ancienneté des données, tentatives de récupération, annulation, pagination, mises à jour après mutation, ainsi que le maintien de données précédentes visibles lors du renouvellement.
Le contexte ne fournit rien de tout cela. Il se contente de transmettre ce que le fournisseur a calculé.
Le fournisseur devient progressivement une mémoire tampon gérée manuellement. Les composants dépendent de ses particularités de mise à jour. Les mutations corrigent manuellement les tableaux partagés. Différents fournisseurs inventent des méthodes de chargement et des formats d’erreur légèrement différents. Laisser une route inactive peut vider la mémoire tampon ; utiliser deux fournisseurs peut entraîner des requêtes doubles.
Des bibliothèques de données dédiées existent parce que l’état distant a un cycle de vie différent de celui de l’état d’interface utilisateur local : propriété sur le serveur, fraîcheur des données, mise en cache, invalidation, concurrence et modes de défaillance.
Context peut distribuer un client configuré ou une dépendance fonctionnelle stable. C’est rarement l’endroit approprié pour reconstituer la coordination des requêtes à l’aide de useState et useEffect ordinaires. Un fournisseur ciblé qui coordonne les données serveur avec un seul flux de travail peut rester valide, mais rendre les données accessibles globalement ne résout pas le problème de synchronisation.
Rendre un en-tête de requête accessible n’est pas la même chose que gérer des données distantes.
Context n’est pas un remplacement pour un modèle d’état
Lorsqu’un flux de travail devient complexe, les équipes l’intègrent parfois dans Context afin que chaque étape puisse accéder aux mêmes champs. Les processus de paiement, d’onboarding, les éditeurs et les formulaires en plusieurs étapes aboutissent souvent de cette manière.
Cela peut fonctionner lorsque le fournisseur représente un seul flux de travail et expose des transitions contrôlées. Cela devient fragile lorsque Context n’est qu’un ensemble de champs et de fonctions de mise à jour.
Sans règles, des combinaisons impossibles apparaissent : une étape de paiement sans livraison valide, des cas où à la fois le succès et l’erreur sont vrais, des options de livraison correspondant à une adresse antérieure. Vous n’avez pas nécessairement besoin d’une bibliothèque formelle de machines à états. Vous devez néanmoins préciser quels faits sont indépendants, quelles combinaisons sont valides, et quels événements modifient plusieurs champs en une seule transition.
Le contexte permet d’atteindre un bon modèle. Il ne peut pas sauver un modèle confus simplement en le plaçant à un niveau supérieur dans l’arborescence.
L’accès global peut affaiblir la propriété
Les valeurs dans le contexte sont faciles à lire et à écrire depuis de nombreux endroits. La commodité peut éroder la propriété.
Un contexte de notification mis à jour par des gestionnaires réseau, des formulaires, des pages et des tâches en arrière-plan. Un contexte de sélection modifié par des tableaux, des dialogues, des raccourcis et des changements de route. Un contexte de préférences écrit directement par chaque contrôle affichant une configuration.
Lorsque de nombreux écrivains partagent un même stockage, la responsabilité en matière de correction devient floue.
Une appropriation plus forte provient d’opérations ciblées. Préférez addNotification, dismissNotification et clearExpiredNotifications à une fonction brute setNotifications. Préférez signIn, signOut et refreshSession à une fonction générique setSession. Les opérations ont un sens et permettent au fournisseur de préserver les invariants.
Même dans ce cas, maintenez le fournisseur concentré. Un dispatcheur universel pour chaque fonctionnalité crée un couplage global sous un nom différent.
De nombreux composants peuvent lire l’utilisateur actuel. Beaucoup moins devraient remplacer la session. Le contexte reste facile à maintenir lorsque le fournisseur gère le cycle de vie et que les consommateurs communiquent via un petit contrat.
Un fournisseur de contexte peut devenir un monolithe caché
Les composants de grande taille se révèlent par la longueur de leur fichier. Les fournisseurs de grande taille restent plus silencieux pendant plus longtemps.
L’arborescence peut sembler modulaire — avec de petits composants et des hooks — tandis qu’un seul fournisseur gère l’état de l’application, les appels réseau, la persistance, les événements du navigateur, les permissions, les notifications, les modaux et les valeurs dérivées. Les fichiers de composants restent courts car le fournisseur s’occupe de la coordination.
Ce n’est toujours qu’un monolithe ; il se contente simplement de générer un peu de JSX.
Le fournisseur change pour des raisons sans rapport. Les règles de notification, les besoins thématiques, les changements de session et les workflows des modaux modifient tous le même module. Chaque consommateur dépend de sa stabilité. Les tests s’élargissent car les opérations interagissent via un état partagé. Les performances se détériorent lorsque les mises à jour se propagent. Les refacteursings obligent à démêler les hypothèses intégrées dans une valeur utilisée partout.
La solution n’est pas un enchaînement infini de niveaux d’encapsulation pour le plaisir. L’encapsulation est acceptable lorsque chaque fournisseur a des responsabilités clairement définies. Quelques fournisseurs bien ciblés sont généralement plus efficaces qu’un seul objet prétendant partager un cycle de vie unique.
Le contexte dans lequel les éléments sont placés est également important. Un contexte nécessaire pour une route donnée devrait englober uniquement ce sous-arbre, et non toute l’application. Un contexte restreint réduit à la fois les dépendances et l’étendue des mises à jour.
La disponibilité globale doit être acquise, et non être le refuge par défaut pour des propriétés gênantes.
Le contexte est excellent pour les problèmes qu’il résout réellement
Le contexte est particulièrement utile lorsque un sous-arbre partage réellement une dépendance environnementale ou une préoccupation stable.
Les thèmes conviennent car de nombreux descendants ont besoin de la même option de présentation. La localisation est adaptée car le formatage et la traduction s’appliquent de manière générale. Un client API partagé, l’environnement de routage, le champ d’une formulaire ou un flux de travail unique peuvent également y appartenir lorsque chaque consommateur se trouve à l’intérieur d’une même limite.
La valeur n’a pas besoin d’être statique. Elle doit avoir un sens cohérent ainsi qu’un schéma de mise à jour adapté aux consommateurs.
Un Context mal conçu entraîne des problèmes tels que la difficulté à transmettre certaines valeurs en tant que propriétés. Cette situation génère des états non liés entre eux, des surfaces de mise à jour larges, des dépendances cachées et des fonctions de modification brutes. La structure interne disparaît tandis que la responsabilité des éléments devient plus difficile à identifier.
Le coût apparaît lorsque l’accès dépasse la capacité de gestion. Des cycles de vie non liés partagent un même fournisseur. Les consommateurs réexécutent des traitements pour des modifications qu’ils ignorent. Les composants fonctionnels dépendent d’environnements cachés. Les fournisseurs se transforment silencieusement en entrepôts improvisés, en caches de requêtes et en moteurs de flux de travail qui n’ont jamais reçu de modèle explicite.
Décrire Context comme une bibliothèque d’état insuffisante manque le point essentiel. Elle n’a jamais prétendu définir la gestion de l’état ; elle se contente de transmettre une valeur le long d’une structure hiérarchique.
La conception globale doit toujours répondre à la question de savoir ce que représente cette valeur, quel module en est propriétaire, qui a le droit de la modifier, avec quelle fréquence les mises à jour sont déclenchées, et quels consommateurs en relèvent réellement.
Context devient le plus efficace une fois ces décisions clairement établies.