As regras dos hooks são duas: chame hooks só no nível superior do seu componente, nunca dentro de uma condição, de um loop, de uma função aninhada ou depois de um return antecipado; e chame-os só a partir de componentes de função ou de hooks personalizados. Elas existem porque o React diferencia os hooks pela ordem em que você os chama, então essa ordem precisa ser a mesma em toda renderização.
O React guarda os valores dos hooks de um componente em uma lista ligada a esse componente. A cada renderização ele percorre a lista: a primeira chamada de hook recebe o primeiro espaço, a segunda chamada o segundo espaço, e assim por diante. Não há nomes, só posições. O exemplo abaixo é uma versão de brinquedo dessa ideia em poucas linhas de JavaScript comum, não o código do próprio React, então você pode quebrá-lo sem medo.
A primeira renderização preenche três espaços: Ada, Countess, 36. Clique em "Render without nickname": a chamada condicional é pulada, então a chamada de age pega o espaço 1 e lê Countess. O React real percebe a divergência em alguns casos e lança um erro, e em outros entrega o valor errado ao hook errado, o que é pior. Mova a chamada toyUseState('Countess') para cima do if e os dois botões dão a idade certa.
As duas regras em código
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
}
}
Uma chamada de hook que é sempre alcançada, na mesma ordem, pode ficar em qualquer lugar do corpo, desde que venha antes de qualquer return antecipado.
Corrigir um hook chamado condicionalmente
O linter reporta isso como React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. A correção é sempre a mesma: chame o hook toda vez e coloque a condição dentro dele.
// 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;
}
O efeito é declarado em toda renderização, e o if dentro dele decide se faz alguma coisa. Faça login e logout algumas vezes e o Console mostra um subscribe para cada login e um unsubscribe para cada logout.
Hooks depois de um retorno antecipado
Um return antecipado também é uma condição. Isto produz "Rendered more hooks than during the previous render" na primeira vez que loading vira false, porque essa renderização alcança um hook que a anterior nunca chamou. Quando uma renderização chama menos hooks que a anterior, a mensagem é "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} />;
}
Todos os hooks ficam acima do return, então os dois ramos chamam os mesmos hooks. Clique em "Toggle loading", expanda a lista e alterne o loading duas vezes: a lista lembra que estava expandida, porque o estado nunca foi pulado.
Hooks em um loop
Você não pode chamar useState uma vez por item em um loop, porque o número de itens muda. Dê a cada item o próprio componente e chame o hook ali; cada instância tem os próprios espaços.
"Invalid hook call"
A mensagem começa com "Invalid hook call. Hooks can only be called inside of the body of a function component." Ela tem três causas comuns:
- O hook executa quando o React não está renderizando um componente de função. Em um event handler, dentro da callback de um efeito, em um componente de classe ou no nível do módulo. Mova-o para o corpo do componente ou para um hook personalizado e chame o hook personalizado a partir do componente.
- As versões de
reactereact-domnão batem. Instale a mesma versão dos dois. - Há duas cópias do React. Um pacote local linkado ou uma biblioteca que empacota o próprio React traz uma segunda cópia, e os hooks de uma cópia não funcionam com o renderizador da outra. Rode
npm ls reacte garanta que só apareça uma versão.
npm ls react
A prévia aqui roda como um build de produção, que não imprime essas mensagens: um hook chamado em um event handler falha com um TypeError comum como "Cannot read properties of null", e os erros do próprio React chegam como códigos curtos "Minified React error". Rode o seu app em modo de desenvolvimento para ler o texto completo.
Deixe o linter garantir as regras
O eslint-plugin-react-hooks verifica as duas regras enquanto você digita e também verifica os arrays de dependências dos efeitos. O template React do Vite e a configuração de ESLint do Next.js o incluem. Em uma configuração flat do ESLint, ligue o preset recomendado dele:
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([reactHooks.configs.flat.recommended]);
As duas regras principais são react-hooks/rules-of-hooks (um erro) e react-hooks/exhaustive-deps (um aviso). Desde a versão 7, o preset também liga regras que vêm do React Compiler, como react-hooks/purity e react-hooks/refs, que apontam renderizações impuras e a leitura de refs durante a renderização. Para escolher as regras uma a uma, registre o plugin como plugins: { 'react-hooks': reactHooks } e liste as regras que quiser.
O plugin encontra os hooks pelo nome: qualquer função cujo nome começa com use seguido de uma letra maiúscula é tratada como hook, e só componentes (nomes com maiúscula) e outras funções use podem chamá-la. É por isso que os hooks personalizados precisam usar o prefixo.
A exceção: use
O use, adicionado no React 19, lê uma promise ou um context e, ao contrário de todos os outros hooks, pode ser chamado dentro de uma condição ou de um loop. Ele ainda precisa ser chamado durante a renderização, dentro de um componente ou de um hook.
Só o título temático muda de cor quando você troca o tema. Tente a mesma coisa com useContext dentro do if e o linter aponta o problema. A página do hook use mostra como ler promises com ele.
Perguntas frequentes
Quais são as regras dos hooks?
São duas. Chame hooks só no nível superior de um componente de função ou de um hook personalizado, nunca dentro de condições, loops, funções aninhadas ou depois de um retorno antecipado. E chame hooks só a partir de componentes de função ou de hooks personalizados, não de funções comuns nem de componentes de classe.
Por que hooks não podem ser chamados condicionalmente?
O React não conhece os seus hooks pelo nome. Ele guarda os valores deles em uma lista e os devolve na ordem em que as chamadas acontecem. Se uma condição pula uma chamada, todo hook seguinte lê o valor que pertencia ao hook anterior.
O que significa "Rendered more hooks than during the previous render"?
Uma renderização chamou mais hooks do que a renderização anterior, normalmente porque um hook está dentro de um if ou depois de um return antecipado que desta vez executou de outro jeito. Mova todos os hooks para cima da condição e coloque a condição dentro do hook.
Como corrijo "Invalid hook call"?
Confira três coisas: o hook executa enquanto o React renderiza um componente de função (não em um event handler, na callback de um efeito, em um componente de classe ou no nível do módulo), react e react-dom têm versões iguais e o app tem só uma cópia do React (npm ls react lista as cópias).
O hook use pode ser chamado condicionalmente?
Sim. O use (React 19) é a única exceção: você pode chamá-lo dentro de blocos if e de loops. Ele ainda precisa ser chamado dentro de um componente ou de um hook, não em um event handler.