Comment corriger l'erreur « Invalid Hook Call » dans React
Corrigez les erreurs invalid hook call de React en vérifiant la pile, les Rules of Hooks, les doublons de React et les versions react-dom.
L’erreur invalid hook call a trois causes courantes : une violation des Rules of Hooks dans votre propre code, plusieurs copies de React dans l’application, ou des versions incompatibles de react et react-dom. La stack trace vous indique laquelle vérifier en premier.
Elle apparaît souvent alors que le code de vos composants est parfaitement correct. Vous faites un npm link sur une bibliothèque de composants locale, vous ajoutez une dépendance ou vous restructurez un monorepo, et l’erreur surgit sans vous dire laquelle des trois causes vous concerne.
Points clés
- Si l’appel de hook fautif se trouve dans le fichier de votre propre composant, le problème vient de l’endroit où le hook est appelé ; s’il se trouve dans
node_modules, il s’agit presque toujours d’une seconde copie de React. - Exécutez
npm ls react(oupnpm why react, ouyarn why react) ; si la sortie fait apparaître plusieurs versions de React, les copies dupliquées sont votre cause et aucune modification du code des composants n’y changera quoi que ce soit. - Une bibliothèque de composants doit déclarer React dans ses
peerDependencieset l’exclure de son build ; si elle embarque sa propre copie de React, chaque application consommatrice se retrouve avec deux copies. - La règle de lint
rules-of-hooksdétecte les appels de hooks mal placés avant même l’exécution du code, mais aucun linter ne peut détecter une copie dupliquée de React, car cette défaillance réside dans l’arbre de dépendances installé, pas dans les sources. - En production, l’erreur se présente sous la forme de l’erreur minifiée #321 : décodez-la sur le error decoder de React avant de spéculer sur la cause.
Que signifie l’erreur « Invalid Hook Call » ?
React lève cette erreur dès qu’un hook s’exécute en dehors du rendu d’un composant fonction, et le message lui-même énumère les possibilités :
Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
La page d’avertissement de React sur les appels de hooks invalides couvre les trois cas, ainsi qu’une section fourre-tout pour les situations plus rares. La suite de cet article les examine dans l’ordre correspondant à la façon dont l’erreur se manifeste habituellement.
Commencez par lire la stack trace
Avant de toucher à la moindre configuration, répondez à une seule question à partir de la stack trace : la frame qui appelle le hook se situe-t-elle dans vos propres fichiers source, ou dans node_modules ? Si elle pointe vers votre fichier de composant, vous avez une violation des Rules of Hooks et le correctif se trouve dans votre code. Si elle pointe vers une dépendance qui fonctionnait auparavant, vous avez presque à coup sûr deux copies de React, et aucune modification apportée à un composant ne changera le résultat.
| Cause | Comment la confirmer | Correctif |
|---|---|---|
| Violation des Rules of Hooks | La stack trace pointe vers vos fichiers | Déplacer le hook au niveau supérieur d’un composant |
| Deux copies de React | npm ls react fait apparaître deux versions | Dédupliquer l’arbre de dépendances (voir ci-dessous) |
Incompatibilité react/react-dom | npm ls react react-dom affiche des versions différentes | Installer les deux ensemble |
Corriger une erreur « Invalid Hook Call » dans votre propre code
Deux règles seulement produisent cette erreur : les hooks doivent être appelés pendant le rendu d’un composant fonction (ou depuis un hook personnalisé appelé par un composant), et ils doivent se situer au niveau supérieur de ce composant, pas dans un if, une boucle ou une fonction imbriquée. Un hook au niveau du module, dans un gestionnaire d’événements ou dans une simple fonction utilitaire enfreint la première règle ; un hook à l’intérieur d’une condition ou d’un callback .map enfreint la seconde.
Le cas de la fonction utilitaire est celui qui surprend le plus, car le code semble tout à fait raisonnable :
// Wrong: buildLink is a plain function, not a component
export function buildLink() {
const { pathname } = useLocation(); // invalid hook call
return `https://example.com${pathname}`;
}
// Right: call the hook in a component, pass the value down
function Page() {
const { pathname } = useLocation();
return <a href={buildLink(pathname)}>Canonical</a>;
}
export function buildLink(pathname) {
return `https://example.com${pathname}`;
}
Pour le cas de la boucle, le correctif est structurel : extrayez un composant enfant afin que chaque élément possède son propre état.
// Wrong: one hook call per array item
function List({ items }) {
return items.map((item) => {
const [open, setOpen] = useState(false); // invalid hook call
return <li key={item.id}>{item.name}</li>;
});
}
// Right: each row is a component with its own state
function Row({ item }) {
const [open, setOpen] = useState(false);
return <li onClick={() => setOpen(!open)}>{item.name}</li>;
}
function List({ items }) {
return items.map((item) => <Row key={item.id} item={item} />);
}
Pourquoi deux copies de React cassent-elles les hooks ?
Les hooks ne fonctionnent que si votre application et react-dom chargent tous deux le même module react. Si chacun obtient sa propre copie, React lève cette erreur alors même que chaque appel de hook dans votre code se trouve exactement là où il devrait. Vérifiez ce point avant toute autre chose :
npm ls react # npm
pnpm why react # pnpm
yarn why react # yarn
pnpm why et yarn why remontent depuis un paquet jusqu’à ce qui l’a fait entrer dans l’arbre, ce qui vous permet de voir exactement quelle dépendance entraîne la seconde copie. Deux situations expliquent la majorité des doublons :
Un paquet local lié (linked). Une bibliothèque liée avec npm link ou pnpm link résout React depuis son propre node_modules, pas le vôtre : c’est pourquoi l’erreur apparaît souvent au moment précis où vous liez une bibliothèque de composants qui fonctionnait très bien lorsqu’elle était installée normalement. La documentation de React traite le cas de npm link, où le correctif consiste à faire pointer la bibliothèque vers le React déjà installé dans l’application. Dans les projets Vite, listez les paquets dans resolve.dedupe et Vite épinglera chacun d’eux sur une copie unique prise à la racine du projet :
// vite.config.js
export default {
resolve: { dedupe: ['react', 'react-dom'] },
}
Une bibliothèque qui embarque React. Si un paquet déclare react comme dépendance normale, ou l’intègre à son build, chaque consommateur se retrouve avec deux copies. Le correctif côté bibliothèque consiste à déclarer React dans les peerDependencies avec la plage de versions supportée et à le marquer comme externe lors du build. Le contournement côté application force une résolution unique, et le nom du champ dépend de votre gestionnaire de paquets. npm lit overrides, où $react signifie « la même version que celle que je déclare moi-même pour react » :
{ "overrides": { "react": "$react", "react-dom": "$react-dom" } }
yarn lit quant à lui resolutions, avec une version explicite comme valeur. Ne mettez pas les deux champs dans un même fichier ; chaque gestionnaire ignore la clé de l’autre.
Versions incompatibles de react et react-dom
react et react-dom sont publiés par paire : vérifiez donc les deux et installez-les en une seule commande. Exécutez npm ls react react-dom ; si les deux versions diffèrent, réinstallez-les ensemble (npm install react react-dom) afin qu’elles se résolvent sur la même version. C’est la cause la plus rapide à écarter, et l’écarter tôt vous évite de courir après des bugs de code imaginaires.
Comment la détecter plus tôt avec un linter ?
Le paquet eslint-plugin-react-hooks signale, au moment de l’édition, toutes les causes de cette erreur liées au code. Avec la configuration flat d’ESLint :
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([reactHooks.configs.flat.recommended]);
Pour les versions d’ESLint antérieures à 9.0.0, la forme historique est "extends": ["plugin:react-hooks/recommended"]. Les projets Next.js bénéficient déjà de ces règles via eslint-config-next. La règle rules-of-hooks détecte les appels de hooks conditionnels ou mal placés avant même l’exécution du code, mais aucun linter ne peut détecter une copie dupliquée de React ou une incompatibilité de versions ; ces défaillances n’existent que dans l’arbre de dépendances installé et ne se manifestent donc qu’à l’exécution.
La forme en production : l’erreur minifiée #321
Dans un build de production, cette erreur se présente sous la forme d’un code d’erreur minifié plutôt que du message complet : décodez donc le code avant de présumer du problème. L’erreur React #321 correspond au texte de l’invalid hook call ; le confirmer d’emblée vous évite de déboguer le mauvais invariant. La stack minifiée nomme rarement le composant fautif, ce qui rend le cas de la copie dupliquée particulièrement difficile à tracer en production. Un outil de session replay comme OpenReplay, qui capture l’erreur console en même temps que la route et l’interaction qui l’a précédée, montre quel arbre de composants était en cours de montage au moment de l’exception, ce qui pointe généralement vers le chunk chargé en lazy-loading ou le widget tiers qui a introduit la seconde copie de React.
Commencez par la stack trace
Traitez cette erreur comme un problème d’aiguillage, pas comme un mystère : la stack trace vous renvoie soit vers votre propre composant (corrigez l’emplacement du hook), soit vers l’arbre de dépendances (exécutez npm ls react et dédupliquez). Commencez par cette seule commande ; elle tranche en quelques secondes la plus déroutante des trois causes, et tout ce qui suit relève de correctifs connus.
FAQ
Un hook personnalisé doit-il commencer par « use » pour éviter l'erreur invalid hook call ?
Non. Le préfixe « use » ne provoque ni n'empêche jamais cette erreur d'exécution, car React ne vérifie pas les noms des hooks à l'exécution. Le préfixe compte pour l'outillage : eslint-plugin-react-hooks s'appuie dessus pour reconnaître les hooks et faire respecter les Rules of Hooks, si bien qu'un hook personnalisé mal nommé échappe silencieusement aux vérifications du linter. Renommez-le avec le préfixe afin que les violations soient signalées à l'édition plutôt que dans le navigateur.
Puis-je appeler des hooks dans un composant classe ?
Non. Les hooks ne fonctionnent que dans les composants fonction et dans les hooks personnalisés appelés depuis ceux-ci : appeler useState ou useContext dans une méthode de classe lève donc l'erreur invalid hook call. Pour utiliser un hook aux côtés d'une classe que vous ne pouvez pas réécrire, créez un petit composant fonction qui appelle le hook et transmet le résultat au composant classe via des props, ou convertissez la classe en composant fonction.
Deux copies de React peuvent-elles coexister sur une même page sans erreur ?
Oui. Deux applications sur une même page peuvent chacune charger leur propre React sans le moindre problème, par exemple lorsque des équipes différentes les livrent séparément. L'erreur n'apparaît que lorsqu'un composant et l'instance de react-dom qui le rend ne s'accordent pas sur le module react qu'ils utilisent. Des copies séparées ne posent aucun problème en soi ; elles cassent dès qu'elles partagent un même arbre de rendu.
Supprimer node_modules et réinstaller corrige-t-il les copies dupliquées de React ?
Uniquement lorsque le doublon provenait d'un état d'installation obsolète ou conflictuel, puisqu'une installation propre permet au gestionnaire de paquets de dédupliquer l'arbre. Si une dépendance déclare react comme dépendance normale, intègre React à son build, ou si vous l'avez liée localement avec npm link, la seconde copie réapparaîtra à chaque installation. Ces cas nécessitent une entrée overrides ou resolutions, une correction des peerDependencies dans la bibliothèque, ou une déduplication au niveau du bundler.
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