Les règles des hooks sont au nombre de deux : appelez les hooks uniquement au niveau supérieur de votre composant, jamais dans une condition, une boucle, une fonction imbriquée ou après un return anticipé ; et appelez-les uniquement depuis des composants fonctions ou des hooks personnalisés. Elles existent parce que React distingue les hooks par l'ordre dans lequel vous les appelez, cet ordre doit donc être le même à chaque rendu.
React stocke les valeurs des hooks d'un composant dans une liste attachée à ce composant. À chaque rendu, il parcourt la liste : le premier appel de hook obtient le premier emplacement, le second appel le second, et ainsi de suite. Il n'y a pas de noms, seulement des positions. L'exemple ci-dessous est une version jouet de cette idée en quelques lignes de JavaScript classique, pas le code de React lui-même, vous pouvez donc le casser sans risque.
Le premier rendu remplit trois emplacements : Ada, Countess, 36. Cliquez sur "Render without nickname" : l'appel conditionnel est sauté, donc l'appel pour age prend l'emplacement 1 et lit Countess. Le vrai React remarque l'incohérence dans certains cas et lève une erreur, et dans d'autres donne la mauvaise valeur au mauvais hook, ce qui est pire. Déplacez l'appel toyUseState('Countess') au-dessus du if et les deux boutons donnent le bon âge.
Les deux règles en code
function Profile({ user }) {
// Good: always called, always in this order
const [tab, setTab] = useState('posts');
const theme = useContext(ThemeContext);
useEffect(() => {
document.title = user.name;
}, [user.name]);
// Bad: inside a condition, a loop or a nested function
if (user.isAdmin) {
const [log, setLog] = useState([]); // breaks the order
}
}
Un appel de hook toujours atteint, dans le même ordre, convient où qu'il se trouve dans le corps, tant qu'il vient avant tout return anticipé.
Corriger un hook appelé sous condition
Le linter le signale ainsi : React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. La correction est toujours la même : appelez le hook à chaque fois, et placez la condition à l'intérieur.
// Broken: the effect exists on some renders and not others
function Notifications({ user }) {
if (user) {
useEffect(() => {
console.log('subscribe for', user.name);
}, [user]);
}
return null;
}
L'effet est déclaré à chaque rendu, et le if à l'intérieur décide s'il faut faire quelque chose. Connectez-vous et déconnectez-vous plusieurs fois et la console affiche un abonnement pour chaque connexion et un désabonnement pour chaque déconnexion.
Des hooks après un retour anticipé
Un return anticipé est aussi une condition. Ce code produit "Rendered more hooks than during the previous render" la première fois que loading passe à false, car ce rendu atteint un hook que le précédent n'a jamais appelé. Quand un rendu appelle moins de hooks que le précédent, le message est "Rendered fewer hooks than expected".
// Broken: useState is skipped while loading
function Comments({ loading, comments }) {
if (loading) return <p>Loading...</p>;
const [expanded, setExpanded] = useState(false);
return <List items={comments} expanded={expanded} />;
}
Chaque hook se trouve au-dessus du return, donc les deux branches appellent les mêmes hooks. Cliquez sur "Toggle loading", dépliez la liste, puis basculez le chargement deux fois : la liste se souvient qu'elle était dépliée, car l'état n'a jamais été sauté.
Des hooks dans une boucle
Vous ne pouvez pas appeler useState une fois par élément dans une boucle, car le nombre d'éléments change. Donnez à chaque élément son propre composant et appelez le hook là ; chaque instance a ses propres emplacements.
« Invalid hook call »
Le message commence par "Invalid hook call. Hooks can only be called inside of the body of a function component." Il a trois causes habituelles :
- Le hook s'exécute alors que React ne fait pas le rendu d'un composant fonction. Dans un gestionnaire d'événement, dans un callback d'effet, dans un composant classe ou au niveau du module. Déplacez-le dans le corps du composant ou dans un hook personnalisé, et appelez le hook personnalisé depuis le composant.
- Les versions de
reactetreact-domne correspondent pas. Installez la même version des deux. - Il y a deux copies de React. Un paquet local lié ou une bibliothèque qui embarque son propre React apporte une seconde copie, et les hooks d'une copie ne fonctionnent pas avec le moteur de rendu de l'autre. Lancez
npm ls reactet assurez-vous qu'une seule version apparaît.
npm ls react
L'aperçu de cette page fonctionne comme un build de production, qui n'affiche pas ces messages : un hook appelé depuis un gestionnaire d'événement échoue avec une simple TypeError comme "Cannot read properties of null", et les erreurs propres à React arrivent sous forme de codes courts "Minified React error". Lancez votre application en mode développement pour lire le texte complet.
Laisser le linter les faire respecter
eslint-plugin-react-hooks vérifie les deux règles pendant que vous tapez, et vérifie aussi les tableaux de dépendances des effets. Le modèle React de Vite et la configuration ESLint de Next.js l'incluent. Dans une configuration ESLint flat, activez son preset recommandé :
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([reactHooks.configs.flat.recommended]);
Les deux règles principales sont react-hooks/rules-of-hooks (une erreur) et react-hooks/exhaustive-deps (un avertissement). Depuis la version 7, le preset active aussi des règles issues de React Compiler, comme react-hooks/purity et react-hooks/refs, qui signalent les rendus impurs et la lecture de refs pendant le rendu. Pour choisir les règles une par une, enregistrez le plugin avec plugins: { 'react-hooks': reactHooks } et listez les règles voulues.
Le plugin repère les hooks par leur nom : toute fonction dont le nom commence par use suivi d'une majuscule est traitée comme un hook, et seuls les composants (noms avec majuscule) et les autres fonctions use peuvent l'appeler. C'est pourquoi les hooks personnalisés doivent utiliser ce préfixe.
L'exception : use
use, ajouté dans React 19, lit une promesse ou un contexte, et contrairement à tous les autres hooks, il peut être appelé dans une condition ou une boucle. Il doit quand même être appelé pendant le rendu, dans un composant ou un hook.
Seul le titre thématisé change de couleur quand vous changez de thème. Essayez la même chose avec useContext dans le if et le linter le signale. La page sur le hook use explique comment lire des promesses avec lui.
Questions fréquentes
Quelles sont les règles des hooks ?
Deux règles. Appelez les hooks uniquement au niveau supérieur d'un composant fonction ou d'un hook personnalisé, jamais dans des conditions, des boucles, des fonctions imbriquées ou après un retour anticipé. Et appelez les hooks uniquement depuis des composants fonctions ou des hooks personnalisés, pas depuis des fonctions ordinaires ni des composants classes.
Pourquoi ne peut-on pas appeler les hooks sous condition ?
React ne connaît pas vos hooks par leur nom. Il stocke leurs valeurs dans une liste et les rend dans l'ordre où les appels ont lieu. Si une condition saute un appel, chaque hook suivant lit la valeur qui appartenait au hook précédent.
Que signifie "Rendered more hooks than during the previous render" ?
Un rendu a appelé plus de hooks que le rendu précédent, en général parce qu'un hook se trouve dans un if ou après un return anticipé qui s'est déroulé différemment cette fois. Placez chaque hook au-dessus de la condition et mettez plutôt la condition à l'intérieur du hook.
Comment corriger "Invalid hook call" ?
Vérifiez trois choses : le hook s'exécute pendant que React fait le rendu d'un composant fonction (pas dans un gestionnaire d'événement, un callback d'effet, un composant classe ou au niveau du module), react et react-dom ont des versions identiques, et l'application n'a qu'une seule copie de React (npm ls react les liste).
Le hook use peut-il être appelé sous condition ?
Oui. use (React 19) est la seule exception : vous pouvez l'appeler dans des blocs if et des boucles. Il doit quand même être appelé dans un composant ou un hook, pas dans un gestionnaire d'événement.