Repérer les effets React dont vous n'avez pas besoin
Utilisez eslint-plugin-react-you-might-not-need-an-effect pour repérer les effets React inutiles et refactoriser l’état dérivé, les événements et les réinitialisations.
eslint-plugin-react-you-might-not-need-an-effect est un plugin ESLint et Oxlint qui signale les appels à useEffect que la documentation de React juge inutiles, par exemple les effets qui dérivent un état, relaient des événements ou font remonter des données vers un composant parent.
Vous avez lu You Might Not Need an Effect, vous avez approuvé d’un hochement de tête, puis vous avez ouvert une base de code comptant quatre cents appels à useEffect, sans aucun moyen pratique d’identifier ceux qui posent problème.
Cet article explique comment installer le plugin, ce qu’il signale sur un composant réaliste, comment refactoriser les quatre cas les plus fréquents, et quand il vaut mieux désactiver un avertissement plutôt que de corriger le code. Pour vous rafraîchir la mémoire sur le hook lui-même, consultez ce guide du hook useEffect de React.
Points clés
- eslint-plugin-react-you-might-not-need-an-effect fournit neuf règles et deux presets pour la flat config :
recommendedsignale chaque règle sous forme d’avertissement, etstrictsous forme d’erreur. Les projets utilisant l’ancien format .eslintrc disposent des presets équivalentslegacy-recommendedetlegacy-strict. - Calculez pendant le rendu les valeurs qui dépendent uniquement des props et de l’état courants, au lieu de les copier dans l’état au moyen d’un effet.
- Pour réinitialiser tout l’état d’un composant lorsqu’une prop, comme un identifiant client, change, effectuez le rendu du composant en utilisant cette prop comme
key. - Les effets qui synchronisent React avec un système externe, comme un timer, une API du DOM, un widget non React ou une connexion réseau, correspondent exactement à la raison d’être de
useEffect: conservez-les. - Désactivez un avertissement avec un commentaire
eslint-disable-next-linequi nomme la règle et indique une justification après--.
Qu’est-ce qui rend un effet React inutile ?
La plupart des effets inutiles prennent l’une de ces deux formes : un effet qui recalcule une valeur et la stocke dans l’état, ou un effet qui surveille une partie de l’état et en met à jour une autre en conséquence. Dans les deux cas, React valide (commit) un rendu avec l’ancienne valeur, exécute l’effet, puis effectue un nouveau rendu avec la valeur corrigée.
Le rendu supplémentaire provoqué par un effet inutile est visible par les utilisateurs. Les replays de session de composants qui abusent des effets peuvent montrer, le temps d’une frame, une liste filtrée vide ou un total provisoire, avant que le second rendu ne la complète. La solution consiste presque toujours à supprimer l’état plutôt qu’à ajuster l’effet.
Installer et exécuter le plugin
Le plugin s’installe en tant que dépendance de développement et s’intègre à la flat config d’ESLint via son preset recommended. Si vous gérez déjà plusieurs plugins ESLint, il cohabite avec eux sans conflit.
npm install --save-dev eslint-plugin-react-you-might-not-need-an-effect
// eslint.config.js
import reactYouMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect";
import globals from "globals";
export default [
reactYouMightNotNeedAnEffect.configs.recommended,
{
languageOptions: {
globals: { ...globals.browser },
parserOptions: { ecmaFeatures: { jsx: true } },
},
},
];
Les exemples ciblent la version 1.0.2, publiée le 20 août 2026 selon le CHANGELOG du plugin. Ce même changelog indique que no-pass-ref-to-parent et no-empty-effect ont été supprimées avant la version 1.0 : retirez-les donc de vos anciennes configurations. Les utilisateurs d’Oxlint chargent le plugin via le champ jsPlugins d’Oxlint. Les identifiants de règles utilisent le préfixe en kebab-case react-you-might-not-need-an-effect/ dans Oxlint, dans les configurations legacy, ainsi que dans la flat config lorsque vous utilisez les presets, car ceux-ci enregistrent le plugin sous le nom de son package. Le préfixe en camelCase reactYouMightNotNeedAnEffect/ ne s’applique que si vous enregistrez vous-même le plugin sous cette clé, comme le fait l’exemple à règle unique du README.
Voici un composant comportant quatre effets, chacun écrit pour correspondre au motif documenté d’une règle :
import { useEffect, useState } from "react";
import { postOrder, useCustomerHistory } from "./api";
export function OrderPanel({ customerId, items, onLoaded }) {
const [total, setTotal] = useState(0);
const [submitted, setSubmitted] = useState(false);
const [note, setNote] = useState("");
const history = useCustomerHistory(customerId);
// Effect 1
useEffect(() => {
setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
}, [items]);
// Effect 2
useEffect(() => {
if (submitted) postOrder(customerId, items, note);
}, [submitted, customerId, items, note]);
// Effect 3
useEffect(() => {
onLoaded(history);
}, [history, onLoaded]);
// Effect 4
useEffect(() => {
setNote("");
setSubmitted(false);
}, [customerId]);
return (
<form onSubmit={(e) => { e.preventDefault(); setSubmitted(true); }}>
<textarea value={note} onChange={(e) => setNote(e.target.value)} />
<p>Total: {total}</p>
<button type="submit">Place order</button>
</form>
);
}
Exécutez npx eslint src/OrderPanel.jsx. Avec recommended, les problèmes détectés apparaissent sous forme d’avertissements, avec l’identifiant de la règle en fin de ligne. Chaque effet correspond à la règle qu’il déclenche :
| Effet | Motif | Règle |
|---|---|---|
| 1 | État calculé à partir des props | no-derived-state |
| 2 | Indicateur d’état utilisé comme déclencheur d’événement | no-event-handler |
| 3 | Données récupérées transmises à un callback du parent | no-pass-data-to-parent |
| 4 | État réinitialisé lorsqu’une prop change | no-adjust-state-on-prop-change (un avertissement par setter) |
L’effet 4 est le cas que résout key. no-reset-all-state-on-prop-change signale ce même motif dans des composants plus simples, mais dans un composant comme celui-ci, qui lit son état pendant le rendu, la version 1.0.2 signale chaque réinitialisation sous no-adjust-state-on-prop-change.
L’ensemble complet des règles, avec la solution de remplacement pour chacune :
| Règle | Remplacer l’effet par |
|---|---|
no-derived-state | Une valeur calculée pendant le rendu |
no-chain-state-updates | Une mise à jour d’état unique et consolidée |
no-event-handler | Du code dans le gestionnaire d’événements |
no-adjust-state-on-prop-change | Un état restructuré ou un calcul effectué au moment du rendu |
no-reset-all-state-on-prop-change | La prop passée en tant que key |
no-pass-live-state-to-parent | Un état remonté (lifted state), ou une valeur renvoyée par un hook |
no-pass-data-to-parent | Une récupération des données dans le parent, avec le résultat transmis en props |
no-external-store-subscription | useSyncExternalStore |
no-initialize-state | Une valeur initiale dans useState() |
Le plugin suit les chaînes d’appels et les tableaux de dépendances, ce qui alourdit le travail du linter. Le profilage des règles d’ESLint (TIMING=1) vous permet de mesurer ce coût sur votre propre dépôt. Le README recommande également d’activer react-hooks/exhaustive-deps, car le plugin suppose que vos tableaux de dépendances sont corrects, ainsi que typescript-eslint/no-floating-promises, qui l’aide à reconnaître les appels de fonctions asynchrones.
État dérivé : le calculer pendant le rendu
Lorsqu’une valeur peut être déduite des props et de l’état dont vous disposez déjà, calculez-la pendant le rendu. N’en conservez pas une copie dans l’état synchronisée par un effet. La documentation de React aborde ce point dans la section mettre à jour un état en fonction des props ou de l’état.
// Before
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
}, [items]);
// After
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
Le total est désormais correct dès le premier rendu. Si le calcul s’avère coûteux, encapsulez-le dans useMemo. Ne le remettez pas dans l’état.
Logique de gestionnaire d’événements : la déplacer dans le gestionnaire
Un effet qui attend un indicateur d’état positionné dans un gestionnaire de soumission ou de clic n’est qu’un gestionnaire d’événements déguisé. Exécutez cette logique directement dans le gestionnaire. La justification est détaillée dans Separating Events from Effects.
// Before
const [submitted, setSubmitted] = useState(false);
useEffect(() => {
if (submitted) postOrder(customerId, items, note);
}, [submitted, customerId, items, note]);
// After
function handleSubmit(e) {
e.preventDefault();
postOrder(customerId, items, note);
}
La version « avant » comporte un véritable bug : modifier la note après la soumission renvoie la commande. Depuis la version 0.10.1, no-event-handler signale également les props utilisées de cette manière, et depuis la 0.10.2, elle signale le problème à l’endroit où la prop est utilisée plutôt qu’à sa déclaration.
Récupération de données et remontée vers le parent
Un composant enfant qui récupère des données et les transmet à son parent via un effet devrait confier cette tâche au parent, au mécanisme de chargement de données du framework ou à une bibliothèque de data fetching, et recevoir le résultat sous forme de props. React décrit cette même inversion dans la section transmettre des données au parent.
// Before: child fetches, then pushes up
const history = useCustomerHistory(customerId);
useEffect(() => {
onLoaded(history);
}, [history, onLoaded]);
// After: parent owns the data
function OrderPage({ customerId, items }) {
const history = useCustomerHistory(customerId); // loader, library, or parent effect
return <OrderPanel customerId={customerId} items={items} history={history} />;
}
Lorsque les données circulent dans un seul sens, on sait clairement quel composant en est propriétaire, et OrderPanel n’a plus besoin de la prop de callback onLoaded dans le seul but de signaler ce qu’il a chargé.
Réinitialiser l’état lors d’un changement de prop : utiliser key
Pour réinitialiser tout l’état d’un composant lorsqu’une prop, comme un identifiant client, change, effectuez le rendu du composant avec cette prop comme key, afin que React monte une nouvelle instance. Ce motif est documenté dans la section réinitialiser tout l’état quand une prop change.
// Before: Effect 4 inside OrderPanel
useEffect(() => {
setNote("");
setSubmitted(false);
}, [customerId]);
// After: delete the effect; in the parent
<OrderPanel key={customerId} customerId={customerId} items={items} history={history} />
no-adjust-state-on-prop-change vise la version partielle de ce motif, où seule une partie de l’état change en réaction à une prop, même si elle signale aussi l’effet 4, comme le montre l’exécution du linter ci-dessus. Lorsque seule une partie de l’état doit être réinitialisée, la solution consiste généralement à restructurer l’état ou à calculer la valeur pendant le rendu.
Faux positifs et effets à conserver
Les effets qui relient un composant à quelque chose que React ne gère pas (timers, API du DOM, widgets non React, connexions réseau) correspondent exactement à ce pour quoi useEffect a été conçu : laissez-les en place. La plupart d’entre eux nécessitent une fonction de nettoyage, comme l’explique Synchronizing with Effects. Si une mesure du DOM provoque un scintillement visible, le choix entre useEffect et useLayoutEffect importe davantage que la question de conserver ou non l’effet.
Les abonnements à un store externe font exception parmi les effets de synchronisation à conserver. Ils relèvent de useSyncExternalStore, qui lit un instantané (snapshot) pendant le rendu au lieu de le copier dans l’état depuis un effet. no-external-store-subscription signale la version basée sur un effet.
L’analyse statique des effets comporte des cas limites. La règle set-state-in-effect de React émet un avertissement lorsqu’un effet appelle un setter d’état de manière synchrone, et une issue React sur la sévérité de set-state-in-effect conteste la rigueur avec laquelle elle traite les indicateurs de montage et les motifs de détection côté client. Considérez les avertissements portant sur ces motifs comme une invitation à vérifier le code, et non comme un verdict dans un sens ou dans l’autre. Utiliser les deux plugins conjointement est raisonnable : la règle de React couvre les appels à setState dans les effets, et ce plugin y ajoute les callbacks vers le parent, les mises à jour en chaîne et les abonnements aux stores.
Avant de désactiver un avertissement, posez-vous les questions suivantes :
- L’effet communique-t-il avec un système extérieur à React ?
- A-t-il besoin d’un nettoyage lorsque les dépendances changent ou que le composant est démonté ?
- Le déplacer dans le rendu ou dans un gestionnaire modifierait-il un comportement visible par les utilisateurs ?
Si les réponses justifient de le conserver, désactivez la règle sur cette ligne et utilisez la syntaxe de description -- d’ESLint pour en consigner la raison :
// eslint-disable-next-line react-you-might-not-need-an-effect/no-initialize-state -- client-only value, avoids hydration mismatch
Ce préfixe correspond à celui des presets, d’Oxlint et des configurations legacy. N’utilisez reactYouMightNotNeedAnEffect/ que si vous avez vous-même enregistré le plugin sous cette clé.
Déploiement
Déployez d’abord eslint-plugin-react-you-might-not-need-an-effect avec le preset recommended, afin que les problèmes détectés apparaissent sous forme d’avertissements et que la CI continue de passer. Traitez-les un composant à la fois, en refactorisant ou en annotant chacun d’eux, puis passez à strict une fois le nombre d’avertissements tombé à zéro, afin que tout nouvel effet inutile fasse échouer le build.
FAQ
Puis-je utiliser eslint-plugin-react-you-might-not-need-an-effect avec une configuration legacy .eslintrc ?
Oui. Les projets utilisant l'ancien format .eslintrc étendent plugin:react-you-might-not-need-an-effect/legacy-recommended pour signaler chaque règle sous forme d'avertissement, ou plugin:react-you-might-not-need-an-effect/legacy-strict pour les signaler sous forme d'erreurs. Dans les configurations legacy, les identifiants de règles et les commentaires de désactivation utilisent le préfixe en kebab-case react-you-might-not-need-an-effect/, le même que celui des presets de la flat config.
Quand faut-il encapsuler une valeur dérivée dans useMemo plutôt que de la calculer à chaque rendu ?
N'utilisez useMemo que lorsque le calcul est lent de manière mesurable. La documentation de React suggère de le chronométrer avec console.time et de le mémoïser si la durée totale atteint environ 1 ms ou plus. useMemo met le résultat en cache entre les rendus tout en le maintenant hors de l'état, si bien qu'il reste correct dès le premier rendu. React Compiler peut mémoïser automatiquement de nombreux calculs, ce qui rend les appels manuels à useMemo pour les valeurs dérivées moins souvent nécessaires.
Modifier la key d'un composant réinitialise-t-il également l'état de ses enfants ?
Oui. Lorsque la key change, React démonte le composant et l'ensemble de son sous-arbre, puis monte de nouvelles instances. Chaque enfant perd son état local, les nœuds du DOM sont recréés, et tous les effets exécutent leur nettoyage avant de s'exécuter à nouveau. Si seule une partie de l'état doit être réinitialisée tandis que l'état des enfants est préservé, ajustez cet état directement ou restructurez-le plutôt que d'utiliser key.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k