Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
Erro resolvidoIntermediáriocódigo testado

Rendered more hooks than during the previous render

O erro que aparece quando um hook fica dentro de if ou depois de um return. Como o React conta hooks por ordem e como reorganizar o componente.

Rodolfo Mori14 min de leitura

Esse erro quer dizer uma coisa só: nesta renderização o componente chamou mais hooks do que na renderização anterior. O React não guarda hook por nome — ele guarda por posição na ordem de chamada. Se a quantidade muda no meio do caminho, ele interrompe o render e joga o erro.

Os exemplos deste artigo são todos do painel da Clínica Patas, uma clínica veterinária: a agenda do dia, a ficha do paciente, o botão de confirmar consulta. Rodei tudo no Node 24.16.0 com React 19.2.8 e jsdom, e toda saída colada aqui saiu do meu terminal.

Um armário numerado pela ordem, não pelo nome

Imagine um armário em que a primeira gaveta guarda o primeiro hook chamado, a segunda guarda o segundo e assim por diante. As gavetas não têm etiquetas como “estado do pet” ou “efeito da agenda”; o React reencontra cada valor apenas pela posição. Se uma condição faz surgir uma gaveta nova no meio, todo o restante fica desalinhado.

Esse é o motivo técnico da regra de ordem dos Hooks. Cada render precisa chamá-los na mesma sequência. Na primeira reprodução, numere as chamadas antes e depois da condição e localize o primeiro número que muda; a tabela e o stack trace confirmam exatamente onde a ordem deixou de bater.

O componente abaixo tem um defeito de uma linha só. Ele mostra um aviso enquanto nenhum paciente está selecionado e, quando um paciente é escolhido, busca a ficha na API.

jsx
import { useState, useEffect } from 'react';
import { buscarFicha } from './api.js';

export function FichaDoPaciente({ pacienteId }) {
  const [ficha, setFicha] = useState(null);

  if (!pacienteId) {
    return <p>Selecione um paciente na agenda.</p>;
  }

  useEffect(() => {
    let ativo = true;
    buscarFicha(pacienteId).then((dados) => {
      if (ativo) setFicha(dados);
    });
    return () => {
      ativo = false;
    };
  }, [pacienteId]);

  return <p>{ficha ? `${ficha.nome} — ${ficha.peso} kg` : 'Carregando ficha...'}</p>;
}

O useEffect está depois do return. Enquanto pacienteId for nulo, esse useEffect nunca chega a ser chamado. O roteiro que reproduz o problema tem duas renderizações: primeiro sem paciente, depois com paciente.

jsx
import { raiz } from './dom.mjs';
import { act } from 'react';
import { createRoot } from 'react-dom/client';
import { FichaDoPaciente } from './ficha.jsx';

const root = createRoot(raiz);

await act(() => {
  root.render(<FichaDoPaciente pacienteId={null} />);
});
console.log('render 1:', raiz.innerHTML);

await act(() => {
  root.render(<FichaDoPaciente pacienteId="P-4471" />);
});
console.log('render 2:', raiz.innerHTML);
render 1: <p>Selecione um paciente na agenda.</p> React has detected a change in the order of Hooks called by FichaDoPaciente. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks   Previous render Next render ------------------------------------------------------ 1. useState useState 2. undefined useEffect ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   Error: Rendered more hooks than during the previous render. at updateWorkInProgressHook (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:7848:17) at updateEffectImpl (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:8642:18) at Object.useEffect (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:26461:9) at process.env.NODE_ENV.exports.useEffect (/private/tmp/clinica-patas/node_modules/react/cjs/react.development.js:1225:34) at FichaDoPaciente (/private/tmp/clinica-patas/ficha.jsx:11:3) at Object.react_stack_bottom_frame (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20) at renderWithHooks (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22)

Três coisas dessa saída resolvem quase todo caso na prática.

A primeira é que o aviso vem antes do erro, e é o aviso que tem a informação útil. A tabela compara a lista de hooks do render anterior com a do render seguinte, linha a linha. O undefined na coluna da esquerda significa “aqui não havia hook nenhum”. A fila de acentos circunflexos marca o slot em que a comparação falhou — no caso, o slot 2.

A segunda é que o erro em si não diz nada além da frase. Não adianta procurar detalhe nele.

A terceira é que a linha do seu código está no meio da pilha, não no topo: at FichaDoPaciente (/private/tmp/clinica-patas/ficha.jsx:11:3). A linha 11 do arquivo é exatamente o useEffect. Os quadros acima são todos internos do React. Cortei a cauda da pilha aqui e nos outros blocos deste artigo: abaixo do último quadro colado só vem mais função interna do React, nada do seu código.

Por que o React identifica hook por posição, e não por nome

O React não sabe que aquele useState se chama ficha. Para ele, cada componente montado carrega uma lista encadeada de slots, preenchida na ordem em que os hooks foram chamados no primeiro render. Nos renders seguintes, ele percorre a mesma lista na mesma ordem: primeira chamada pega o slot 1, segunda chamada pega o slot 2, e assim por diante.

Dá para enxergar isso trocando os hooks por um invólucro que registra a posição antes de delegar para o React:

js
import * as React from 'react';

let slot = 0;

export function abrirRender(componente) {
  slot = 0;
  console.log(`render de <${componente}>`);
}

export function useState(inicial) {
  console.log(`   slot ${++slot} -> useState`);
  return React.useState(inicial);
}

export function useEffect(efeito, deps) {
  console.log(`   slot ${++slot} -> useEffect`);
  return React.useEffect(efeito, deps);
}

Trocando os imports da FichaDoPaciente por esses, acrescentando abrirRender('FichaDoPaciente') na primeira linha do corpo e rodando o mesmo roteiro de duas renderizações, os slots aparecem:

render de <FichaDoPaciente> slot 1 -> useState render de <FichaDoPaciente> slot 1 -> useState slot 2 -> useEffect render de <FichaDoPaciente> slot 1 -> useState slot 2 -> useEffect >> Rendered more hooks than during the previous render.

O primeiro render fecha com um slot só. O segundo pede dois — e não existe slot 2 para casar. O terceiro bloco não é uma renderização nova da sua aplicação: quando um render lança, o React refaz aquela mesma renderização uma vez antes de propagar o erro, e a segunda tentativa bate na mesma parede. E isso não é instrumentação do modo de desenvolvimento: trocando o act por flushSync e rodando com NODE_ENV=production, as três passadas continuam aparecendo.

render anterior render seguinte slot 1 · useState slot 2 · não existe slot 1 · useState slot 2 · useEffect casa não casa — o React interrompe aqui

Se você nunca reparou em quantas vezes um componente é renderizado, vale ler por que o componente roda de novo antes de seguir: a lista de hooks é percorrida inteira a cada um desses re-renders.

O par de hooks que troca de lugar sem erro nenhum

Aqui está a prova de que o React só olha para a posição. O card do paciente tem dois useState, e alguém escreveu a ordem invertida no modo de edição:

jsx
import { useState } from 'react';

export function CardPaciente({ modoEdicao }) {
  let nome, peso;

  if (modoEdicao) {
    [peso] = useState(4.2);
    [nome] = useState('Nina');
  } else {
    [nome] = useState('Nina');
    [peso] = useState(4.2);
  }

  return (
    <p>
      {nome}{peso} kg
    </p>
  );
}

A contagem não muda: são dois hooks nos dois caminhos. Renderizando primeiro em leitura e depois em edição:

leitura : Nina — 4.2 kg edicao : 4.2 — Nina kg

Nenhum erro. Nenhum aviso. O peso virou “Nina” e o nome virou 4.2, porque o slot 1 sempre devolve o que foi guardado no slot 1 — e no render de edição quem leu o slot 1 foi a variável peso.

O return antecipado que engole o useEffect

Essa é a causa número um, e ela se disfarça de várias formas. Todas têm o mesmo efeito: em algum caminho de execução, uma chamada de hook é pulada.

forma o que muda na contagem o que o React faz
hook depois de um return antecipado de N para N+1 quando a condição inverte Rendered more hooks…
hook depois de um return dentro de um catch cai para zero naquele render nada — o estado é remontado depois
hook dentro de um if que embrulha todos os hooks zero num render, N no outro nada — o estado é remontado
hook dentro de map ou for acompanha o tamanho da lista Rendered more ou fewer hooks…
hook dentro de um handler de evento não roda durante o render Invalid hook call

As duas linhas do meio surpreendem quem só conhece a primeira. Veja o cabeçalho da ficha, que só mostra o campo de anotação para quem é veterinário:

jsx
import { useState } from 'react';

export function CabecalhoDaFicha({ ehVeterinario }) {
  if (ehVeterinario) {
    const [rascunho, setRascunho] = useState('');
    return (
      <textarea
        value={rascunho}
        onChange={(e) => setRascunho(e.target.value)}
        placeholder="Anotação da consulta"
      />
    );
  }

  return <p>Ficha em modo somente leitura.</p>;
}

Renderizei em modo veterinário, digitei uma anotação, troquei para somente leitura e voltei:

1) veterinario digitou : Nina: peso estavel, retorno em 30 dias 2) virou so leitura : Ficha em modo somente leitura. 3) voltou para edicao : ""

Nenhum erro — e a anotação sumiu. O motivo é que o render do meio não chamou hook nenhum, então a lista do componente ficou vazia; no render seguinte o React tratou o componente como se estivesse montando pela primeira vez e devolveu o valor inicial. Um if que embrulha todos os hooks não estoura: ele apaga estado em silêncio.

Hook dentro de laço e hook dentro de callback

Na agenda do dia, o estado “confirmada” foi parar dentro do map:

jsx
import { useState } from 'react';

export function AgendaDoDia({ consultas }) {
  const [busca] = useState('');

  return (
    <ul>
      {consultas.map((consulta) => {
        const [confirmada] = useState(false);
        return (
          <li key={consulta.id}>
            {consulta.paciente} {confirmada ? '(confirmada)' : '(pendente)'} {busca}
          </li>
        );
      })}
    </ul>
  );
}

Com duas consultas funciona. Quando chega a terceira do dia, a contagem sobe de 3 para 4:

2 consultas: Nina (pendente) Thor (pendente) React has detected a change in the order of Hooks called by AgendaDoDia. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks   Previous render Next render ------------------------------------------------------ 1. useState useState 2. useState useState 3. useState useState 4. undefined useState ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   Error: Rendered more hooks than during the previous render. at updateWorkInProgressHook (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:7848:17) at updateReducer (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:7967:18) at Object.useState (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:26511:18) at process.env.NODE_ENV.exports.useState (/private/tmp/clinica-patas/node_modules/react/cjs/react.development.js:1263:34) at <anonymous> (/private/tmp/clinica-patas/agenda.jsx:9:30) at Array.map (<anonymous>) at AgendaDoDia (/private/tmp/clinica-patas/agenda.jsx:8:18)

Repare no quadro at Array.map (<anonymous>) no meio da pilha: ele é a assinatura desse caso. Sempre que você vir Array.map entre o seu componente e o hook, o problema é hook dentro de laço. Se a sua lista veio de uma API, vale conferir também como renderizar listas com a prop key, que é o outro erro clássico do mesmo map.

Já o hook dentro de um handler dá um erro diferente. O botão de confirmar consulta:

jsx
import { useState } from 'react';

export function BotaoConfirmar({ consulta }) {
  function confirmar() {
    const [enviando, setEnviando] = useState(false);
    if (!enviando) setEnviando(true);
    console.log(`confirmando ${consulta}...`);
  }

  return <button onClick={confirmar}>Confirmar consulta</button>;
}

O componente monta sem reclamar. O erro só aparece no clique:

montou: <button>Confirmar consulta</button> Error: 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 See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem. at Object.throwInvalidHookError (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:7597:13) at process.env.NODE_ENV.exports.useState (/private/tmp/clinica-patas/node_modules/react/cjs/react.development.js:1263:34) at confirmar (/private/tmp/clinica-patas/botao.jsx:5:37) at executeDispatch (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:19116:9)

Mensagem diferente porque a causa é diferente: fora do render, o React nem tem uma lista de hooks aberta para contar. A correção é subir o useState para o corpo do componente e deixar no handler só a chamada de setEnviando.

A variação “Rendered fewer hooks than expected”

É o mesmo defeito, com os dois renders na ordem inversa. Voltando à FichaDoPaciente do começo, agora com paciente primeiro e nulo depois:

jsx
await act(() => root.render(<FichaDoPaciente pacienteId="P-4471" />));
console.log('render 1:', raiz.innerHTML);

await act(() => root.render(<FichaDoPaciente pacienteId={null} />));
console.log('render 2:', raiz.innerHTML);
render 1: <p>Carregando ficha...</p> Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement. at finishRenderingHooks (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:7717:15) at renderWithHooks (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:7684:7) at updateFunctionComponent (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19) at beginWork (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:11778:18)

A mensagem é mais gentil — ela já sugere o return antecipado. Em compensação, o diagnóstico é pior: não vem tabela, não vem o nome do componente e não vem nenhum quadro do seu código na pilha. Faz sentido, porque o erro é detectado no fim do render, quando o React percebe que sobrou slot na lista, e não no momento em que um hook faltou.

E existe um caso em que nem esse erro sai: quando o render passa a chamar zero hooks. É o que acontece no CabecalhoDaFicha da seção anterior. O React só reclama de hook faltando se pelo menos um hook rodou e sobrou slot depois dele.

A condição escondida dentro de um hook customizado

Este é o caso que mais toma tempo, porque o componente parece impecável. O hook customizado é que tem o return antecipado:

js
import { useState, useEffect } from 'react';
import { buscarFicha } from './api.js';

export function useFichaDoPaciente(pacienteId) {
  const [ficha, setFicha] = useState(null);

  if (!pacienteId) return null;

  useEffect(() => {
    buscarFicha(pacienteId).then(setFicha);
  }, [pacienteId]);

  return ficha;
}
jsx
import { useFichaDoPaciente } from './use-ficha.js';

export function PainelDaClinica({ pacienteId }) {
  const ficha = useFichaDoPaciente(pacienteId);

  return <p>{ficha ? ficha.nome : 'Nenhuma ficha aberta.'}</p>;
}
render 1: Nenhuma ficha aberta. React has detected a change in the order of Hooks called by PainelDaClinica. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks   Previous render Next render ------------------------------------------------------ 1. useState useState 2. undefined useEffect ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   Error: Rendered more hooks than during the previous render. at updateWorkInProgressHook (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:7848:17) at updateEffectImpl (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:8642:18) at Object.useEffect (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.development.js:26461:9) at process.env.NODE_ENV.exports.useEffect (/private/tmp/clinica-patas/node_modules/react/cjs/react.development.js:1225:34) at useFichaDoPaciente (file:///private/tmp/clinica-patas/use-ficha.js:9:3) at PainelDaClinica (/private/tmp/clinica-patas/painel.jsx:4:17)

O aviso acusa PainelDaClinica, que não tem defeito nenhum — o painel só chama um hook, uma vez, incondicionalmente. Quem tem o defeito é useFichaDoPaciente, e ele aparece só na pilha. É por isso que a pilha vale mais que a mensagem: a lista de hooks pertence ao componente, e tudo que os hooks customizados chamam entra na mesma lista.

A correção: hooks primeiro, condição depois

A regra prática cabe numa linha: nenhum return antes do último hook. Todos os hooks ficam no topo do corpo, sempre, e a condição desce para depois deles. Dentro do efeito, um if normal decide se ele faz alguma coisa:

jsx
import { useState, useEffect } from 'react';
import { buscarFicha } from './api.js';

export function FichaDoPaciente({ pacienteId }) {
  const [ficha, setFicha] = useState(null);

  useEffect(() => {
    if (!pacienteId) return;

    let ativo = true;
    buscarFicha(pacienteId).then((dados) => {
      if (ativo) setFicha(dados);
    });
    return () => {
      ativo = false;
    };
  }, [pacienteId]);

  if (!pacienteId) {
    return <p>Selecione um paciente na agenda.</p>;
  }

  return <p>{ficha ? `${ficha.nome} — ${ficha.peso} kg` : 'Carregando ficha...'}</p>;
}
sem paciente : Selecione um paciente na agenda. logo apos : Carregando ficha... ficha pronta : Nina — 4.2 kg voltou : Selecione um paciente na agenda.

Repare que o return antecipado continuou existindo. Ele nunca foi o problema; o problema era o hook depois dele. Se você quiser aprofundar as formas de mostrar uma coisa ou outra na tela, veja renderização condicional no React.

Para o caso do laço, a correção é outra: cada item vira um componente, com a própria lista de hooks.

jsx
import { useState } from 'react';

function LinhaDaConsulta({ paciente }) {
  const [confirmada, setConfirmada] = useState(false);

  return (
    <li>
      {paciente} {confirmada ? '(confirmada)' : '(pendente)'}
      <button onClick={() => setConfirmada(true)}>ok</button>
    </li>
  );
}

export function AgendaDoDia({ consultas }) {
  return (
    <ul>
      {consultas.map((consulta) => (
        <LinhaDaConsulta key={consulta.id} paciente={consulta.paciente} />
      ))}
    </ul>
  );
}

Confirmei a consulta da Nina e depois acrescentei a terceira consulta do dia:

2 consultas: Nina (confirmada) | Thor (pendente) | 3 consultas: Nina (confirmada) | Thor (pendente) | Bidu (pendente) |

Agenda cresceu, nada quebrou, e a confirmação da Nina continuou de pé — porque cada LinhaDaConsulta tem o próprio slot 1.

Em produção o mesmo defeito vira “Minified React error #310”

Se o erro chegou ao ambiente publicado, a mensagem muda de cara. Rodei a versão quebrada com NODE_ENV=production, que faz o React carregar o build de produção:

render 1: <p>Selecione um paciente na agenda.</p> Error: Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. at updateWorkInProgressHook (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.production.js:4472:13) at updateEffectImpl (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.production.js:5082:14) at Object.updateEffect [as useEffect] (/private/tmp/clinica-patas/node_modules/react-dom/cjs/react-dom-client.production.js:5101:3) at exports.useEffect (/private/tmp/clinica-patas/node_modules/react/cjs/react.production.js:496:33) at FichaDoPaciente (/private/tmp/clinica-patas/ficha.jsx:11:3) render 2:

A checagem continua lá, mas a tabela comparativa e o texto da mensagem, não — eles só existem no build de desenvolvimento. Se alguém abrir um chamado dizendo “deu erro 310 na tela do paciente”, é exatamente este artigo. E veja a última linha: render 2: saiu vazio. A raiz que tinha um parágrafo ficou sem nada, porque um erro de render não capturado desmonta a árvore inteira. É a tela branca que o usuário relata.

O lint que pega isso antes do navegador

Nenhuma das formas da tabela precisa chegar ao navegador. O eslint-plugin-react-hooks reconhece todas elas por análise estática.

bash
npm i -D eslint eslint-plugin-react-hooks
npx eslint ficha.jsx card.jsx cabecalho.jsx agenda.jsx botao.jsx use-ficha.js
js
import reactHooks from 'eslint-plugin-react-hooks';

export default [
  {
    files: ['**/*.{js,jsx}'],
    ...reactHooks.configs.flat['recommended-latest'],
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      parserOptions: { ecmaFeatures: { jsx: true } },
    },
  },
];

Rodando nos seis arquivos quebrados deste artigo, com ESLint 10.9.0 e o plugin na versão 7.1.1:

/private/tmp/clinica-patas/agenda.jsx 9:30 error React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks   /private/tmp/clinica-patas/botao.jsx 5:37 error React Hook "useState" is called in function "confirmar" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use" react-hooks/rules-of-hooks   /private/tmp/clinica-patas/cabecalho.jsx 5:37 error React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks   /private/tmp/clinica-patas/card.jsx 7:14 error React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks 8:14 error React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks 10:14 error React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks 11:14 error React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks   /private/tmp/clinica-patas/ficha.jsx 11:3 error React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks   /private/tmp/clinica-patas/use-ficha.js 9:3 error React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks   ✖ 9 problems (9 errors, 0 warnings)

Duas coisas nessa saída valem o preço da instalação. A primeira é que o plugin pegou justamente os dois arquivos que não geram erro nenhum em tempo de execução: o card.jsx, com os quatro useState apontados um a um, e o cabecalho.jsx, que só apaga em silêncio o que a pessoa digitou. Os dois defeitos mudos deste artigo, que nenhuma renderização denuncia, saem de graça numa análise que nem executa o código. A segunda é a mensagem do cabecalho.jsx e do use-ficha.js, mais específica que as outras: ela pergunta se o hook não teria ficado depois de um return antecipado. Rodando nos dois arquivos corrigidos, o plugin sai calado, com código de saída zero.

Por onde seguir

Se você chegou aqui por causa de um useEffect fora do lugar, o passo seguinte é entender bem o ciclo dele: useEffect, dependências e limpeza explica quando o efeito roda, quando ele roda de novo e o que a função de limpeza resolve. O parente próximo deste erro é o Maximum update depth exceeded, que também nasce de efeito mal posicionado, mas se manifesta como loop em vez de parada. E, para ver onde hooks entram na ordem de estudo, o guia de React tem o mapa inteiro.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • react
  • hooks
  • regras dos hooks
  • useeffect
  • erro

Perguntas frequentes

Posso usar if dentro do useEffect?
Pode, e é justamente essa a saída. A regra vale para a chamada do hook, não para o que acontece dentro dele. A chamada de useEffect acontece em todo render; quem decide se o efeito faz alguma coisa é um if na primeira linha do corpo.
Por que a tela inteira fica em branco quando esse erro acontece?
Porque o erro é lançado durante a renderização, e um erro de render que ninguém captura faz o React desmontar a árvore a partir da raiz. No teste deste artigo, a raiz que tinha um parágrafo ficou com string vazia depois da exceção. Um error boundary acima do componente troca a tela branca por uma mensagem de falha.
useState dentro de map nunca funciona?
Nunca, porque a quantidade de hooks passa a depender do tamanho da lista. A correção é sempre a mesma: cada item vira um componente, e o estado daquele item mora dentro dele. Aí o React tem uma lista de hooks por componente filho, e não uma lista gigante no pai.
E se eu realmente precisar de um número variável de estados?
Guarde tudo em um único useState, com um objeto ou um Map indexado pelo id do item. Um hook só, com um valor que cresce e diminui, é legítimo. O que não pode é a quantidade de chamadas de hook variar.
Trocar a ordem de dois useState do mesmo tipo dá erro?
Não dá erro nenhum, e é por isso que é pior. Como a contagem continua igual e os dois slots são do mesmo tipo, o React entrega os valores trocados em silêncio, e o bug aparece longe da causa, na tela.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, React 19.2.8, jsdom 30.0.1, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. React — Rules of Hooks — react.dev
  2. React — Invalid hook call warning — react.dev
  3. eslint-plugin-react-hooks no npm — npmjs.com

Continue por aqui