26 de out. de 2025·6 min de leitura

Corrigir loops infinitos de re-render no React: armadilhas de padrões gerados por IA

Aprenda a corrigir loops infinitos de re-render no React identificando armadilhas em código gerado por IA e seguindo um fluxo passo a passo para estabilizar atualizações.

Corrigir loops infinitos de re-render no React: armadilhas de padrões gerados por IA

Como é um loop infinito de re-render

Um loop infinito de re-render acontece quando um componente React continua renderizando repetidamente sem nunca estabilizar. Em vez de um ciclo normal de render, algo continua disparando outra atualização.

Os primeiros sinais geralmente são visíveis pelo usuário: a página parece travada, botões param de responder, inputs ficam lentos ou a interface pisca enquanto o estado muda mais rápido do que o navegador consegue renderizar.

Sintomas técnicos comuns:

  • Uma sobreposição de erro vermelha como "Too many re-renders. React limits the number of renders to prevent an infinite loop"
  • Picos de CPU e a aba ficando quente ou lenta
  • A mesma requisição de rede sendo disparada repetidamente
  • Logs no console imprimindo sem parar
  • UI que se reseta sozinha (por exemplo, um modal que reabre instantaneamente depois de você fechá-lo)

Esses loops são mais que um problema de performance. Eles quebram a lógica. Um formulário nunca termina a validação, checagens de auth pulam entre "logado" e "deslogado", e efeitos que deveriam rodar uma vez rodam centenas de vezes. Isso costuma gerar bugs extras como eventos de analytics duplicados, toasts repetidos ou APIs atingindo limites de taxa.

Um cenário comum: um dashboard carrega, refaz a busca, seta o estado com a resposta e então busca de novo porque um efeito depende de algo que muda a cada render. O usuário vê um spinner que nunca termina e seu backend recebe uma chuva de requisições idênticas.

Por que código React gerado por IA costuma entrar em loop

Ferramentas de IA podem produzir React que parece ok numa demonstração e depois desmorona com dados reais e usuários reais. Uma grande razão é misturar três coisas que deveriam ficar separadas:

  • Estado fonte (o que você armazena)
  • Valores derivados (o que você calcula a partir do estado armazenado)
  • Efeitos colaterais (o que você faz porque algo mudou)

Quando isso se mistura, você acaba setando estado durante o render, ou rodando um efeito que muda os mesmos valores que fazem o efeito rodar de novo.

Padrão 1: “Fazer tudo em um useEffect”

Componentes gerados por IA frequentemente empacotam fetch, parsing, filtragem e estado de UI em um único efeito. O efeito roda, chama setState algumas vezes, re-renderiza e então roda de novo porque uma dependência mudou.

O loop normalmente parece: fetch -> setState -> re-render -> effect roda de novo -> fetch de novo.

Padrão 2: Dependências instáveis criadas a cada render

Outra causa comum é colocar valores no array de dependências que são recriados a cada render, mesmo que pareçam “os mesmos”. Exemplos incluem objetos/arrays inline, funções inline ou valores derivados reconstruídos a cada render.

O React compara dependências por referência, não por conteúdo profundo. Uma nova referência de objeto significa “mudou”, então o efeito roda novamente.

Um cenário realista: um dashboard monta queryParams como um objeto dentro do componente e usa isso em useEffect para buscar. Cada render cria um novo objeto, então o efeito dispara de novo, busca de novo, seta estado de novo e o dashboard nunca estabiliza.

Como confirmar onde o loop começa

Primeiro, confirme que é um loop real e não apenas “muitas renders”. Adicione um contador no topo da função do componente.

console.count('Component render')

Se o contador sobe rapidamente mesmo quando você não está interagindo com a página, você confirmou.

Em seguida, isole o gatilho. Desative temporariamente efeitos até o loop parar. A maneira mais rápida é comentar efeitos um por um (ou retornar cedo dentro deles) e recarregar após cada mudança. Quando o loop parar, a última mudança aponta para a origem.

Uma ordem prática:

  • Blocos useEffect que setam estado ou escrevem em storage
  • Efeitos que chamam APIs ou se inscrevem em algo
  • Estado derivado calculado ou sincronizado a cada render
  • Providers de contexto e componentes pais

O React DevTools também ajuda. Ative highlight updates e observe qual parte da UI pisca repetidamente. Isso frequentemente mostra se o loop está no componente atual ou um nível acima.

Se a aba Network mostrar a mesma requisição repetida, a cadeia costuma ser: render -> effect -> fetch -> setState -> render. Corrigir o loop também previne requisições duplicadas e problemas de rate-limit.

Fluxo passo a passo para parar o loop

Trate um loop de re-render como uma reação em cadeia. Uma atualização causa o próximo render, que causa a próxima atualização. Seu trabalho é encontrar o primeiro dominó.

  1. Identifique a atualização que acontece imediatamente antes do próximo render.

Observe qual setter roda (setUser, setItems, setLoading) e o que o dispara. Se houver vários setters, comente-os um a um para ver qual para o ciclo.

  1. Certifique-se de que você não está atualizando estado durante o render.

Um erro comum é chamar um setter enquanto “calcula” valores ou dentro de um helper que roda enquanto o JSX é construído. Atualizações de estado pertencem a handlers de evento, efeitos ou callbacks, não ao corpo do render.

  1. Enxugue o efeito até sua função real.

Reduza o efeito à menor versão que ainda reproduz o bug. Anote do que ele realmente depende (props, estado e quaisquer valores externos que lê). Problemas de dependência muitas vezes se escondem aqui.

  1. Estabilize as entradas e faça atualizações idempotentes.

Algumas correções que param loops rapidamente:

  • Torne entradas instáveis em estáveis (memoize callbacks/objetos ou mova-os para fora do componente)
  • Não armazene valores derivados em estado a menos que realmente precise (calcule a partir de props/estado ou use useMemo)
  • Proteja atualizações para chamar setState apenas quando algo realmente mudou
  • Adicione cleanup para subscriptions, timers e requisições em andamento

Se um efeito “sincroniza” um valor em outro, ele deve ser seguro para rodar mais de uma vez. Rodar um efeito duas vezes não deve mudar o estado a menos que suas entradas realmente tenham mudado.

Corrigindo problemas de dependência do useEffect

Transforme um demo em produção
Corrigimos bounce de autenticação, segredos expostos e problemas comuns de segurança encontrados em código gerado.

A maioria dos loops de useEffect se resume ao mesmo padrão: o efeito seta estado, e essa mudança de estado faz o efeito rodar de novo.

Trate todo setState dentro de um efeito como suspeito até que você consiga explicar por que ele para.

Regra chave: não setar estado incondicionalmente dentro de um efeito. Se o efeito roda no mount e nas mudanças de dependências, você precisa de uma condição que previna atualizações repetidas.

Uma proteção prática é “só atualizar quando o próximo valor for realmente diferente.” Isso importa quando o código reconstrói arrays ou objetos e os armazena no estado a cada vez, mesmo quando o conteúdo não mudou.

Boas correções:

  • Comparar antes de chamar setState (ou comparar dentro do update funcional)
  • Memoizar dependências quando você realmente precisa depender de objetos/funções
  • Calcular valores derivados durante o render ao invés de sincronizá-los via efeito
  • Manter dependências intencionais (depender de primitivos quando possível)

Também não trate o array de dependências como uma checklist. Linters ajudam, mas silenciar um aviso adicionando uma dependência pode transformar um efeito de setup único em um loop auto-disparante. Frequentemente a correção real é reestruturar: dividir um efeito em dois menores ou mover trabalho para um handler de evento.

Atualizações de estado que acidentalmente disparam re-renders

É fácil fixar no useEffect, mas alguns loops vêm de erros simples de estado.

Setar estado durante o render

Se você chama setState no corpo do render, o React não tem escolha senão renderizar de novo. Isso pode ser direto (um setX(...) simples) ou indireto (um helper chamado durante o render que atualiza estado). Mesmo algo que parece inofensivo, como “normalizar dados se estiverem faltando”, pode virar um loop.

Estado espelho (estado derivado perseguindo props)

Outra armadilha é copiar props para estado e então “sincronizar” sempre que não casam. Se a prop é um objeto novo a cada render, sua lógica de sincronização nunca para.

Alguns padrões que frequentemente causam renders repetidos:

  • Atualizar estado durante o render (incluindo em helpers chamados do JSX)
  • Armazenar valores derivados em estado ao invés de calculá-los
  • Criar novos arrays/objetos e setá-los a cada render sem checar igualdade
  • Passar uma prop key mutável que força remounts

Quando o próximo estado depende do estado anterior, use updates funcionais. Em vez de setCount(count + 1), use setCount(c => c + 1). Isso evita valores obsoletos que podem disparar updates de “correção”.

Se você tem várias atualizações relacionadas que ficam se chocando (loading, erros, retries, dados em cache), useReducer pode ajudar ao manter transições em um único lugar.

Fetching de dados, subscriptions e armadilhas de cleanup

Loops frequentemente se escondem dentro de efeitos “normais”: fetches, subscriptions e timers. Se cada callback chama setState, você pode acabar com re-renders constantes mesmo que a UI aparente não mudar.

Fetches que continuam executando

Se um fetch está vinculado a um estado que o próprio fetch atualiza, você obtém um loop.

Torne requisições canceláveis para que respostas obsoletas não sobrescrevam estado mais novo. Use AbortController dentro do efeito e aborte no cleanup.

Para reduzir duplicatas, adicione uma proteção simples usando um ref (não estado), como rastrear uma chave de requisição em andamento.

Subscriptions, timers e cleanup

Listeners e timers podem disparar para sempre se você esquecer do cleanup. Uma regra simples: todo “start” precisa de um “stop” correspondente no cleanup.

Limpe intervals/timeouts, desinscreva listeners e remova handlers de evento.

Uma fonte de dados, um escritor

Evite atualizar a mesma parte do estado a partir de múltiplos lugares. Se um fetch seta profile, uma subscription também seta profile e outro efeito “sincroniza” profile, você criou um loop de feedback. Escolha um dono para as escritas. Os outros podem disparar um refresh, não escrever o mesmo estado.

Erros comuns que mantêm o loop vivo

Diagnostique seus hooks e efeitos
Receba uma análise clara de dependências instáveis, efeitos inseguros e escritas de estado durante o render.

Alguns loops “desaparecem” quando você comenta uma linha, depois voltam quando você a recoloca. Isso normalmente significa que o gatilho subjacente ainda está lá.

StrictMode deixa efeitos inseguros óbvios

Se um efeito roda duas vezes em desenvolvimento, o React StrictMode pode estar fazendo seu trabalho. Não desative o StrictMode para esconder o problema. Torne o efeito seguro para rodar mais de uma vez adicionando cleanup, protegendo atualizações ou movendo inicializações únicas para fora do efeito.

Closures obsoletas criam atualizações de “compensação”

Um padrão comum é um efeito que lê estado antigo e então “corrige” com setState. Essa correção dispara um novo render, que cria outra leitura obsoleta, e o ciclo se repete.

Se um efeito usa estado mas o array de dependências não bate, você pode acabar lutando contra seus próprios valores passados. Prefira updates funcionais quando precisar do valor mais recente.

Memoização também pode atrapalhar. useCallback/useMemo com dependências erradas ainda cria uma nova função/objeto a cada render. Se esse valor ficar num array de dependências, seu efeito vai rodar toda vez.

Maneiras rápidas de detectar um loop que ainda vive:

  • Logar dependências “estáveis” (funções, objetos, arrays) e ver se mudam a cada render
  • Criar objetos derivados dentro do efeito a partir de dependências primitivas
  • Garantir que cada subscription/listener tenha cleanup
  • Evitar sincronizar estado com props a não ser que haja razão clara

Checklist rápido antes de shipar

Faça uma última verificação com comportamento de usuário real em mente. Loops muitas vezes desaparecem no caminho feliz e voltam quando usuários clicam rápido, mudam de aba ou têm redes lentas.

  • Procure setters que possam rodar durante o render (incluindo helpers chamados do JSX)
  • Leia cada useEffect como uma frase: “Quando X muda, faça Y.” Garanta que exista uma condição de parada
  • Verifique estabilidade de dependências (objetos, arrays e funções inline mudam a cada render)
  • Verifique cleanup para timers, listeners, subscriptions e observers
  • Torne o trabalho de rede resiliente: cancele requisições obsoletas, dedupe chamadas e ignore respostas tardias

Um teste simples: abra a página e mude um filtro três vezes rápido. Se você ver requisições sobrepostas e a UI se “corrigindo”, provavelmente tem dependências instáveis e falta de cancelamento.

Um exemplo realista: o dashboard que fica refazendo fetch

Pare o ciclo infinito de re-render
Reparamos código React gerado por IA que entra em loop, refaz buscas e reseta estado inesperadamente.

Um caso comum em admin dashboards gerados: carregar uma lista de usuários, armazená-la em estado e mostrar uma tabela. Tudo parece ok, exceto que a página refaz fetch constantemente e a UI fica com jitter.

O bug

O padrão geralmente começa com um efeito que depende de um objeto inline. Esse objeto é recriado a cada render, então o React trata como “mudou” sempre.

// Problem
function AdminUsers({ orgId }) {
  const [users, setUsers] = React.useState([]);

  const options = { method: "GET", headers: { "x-org": orgId } }; // new each render

  React.useEffect(() => {
    fetch("/api/users", options)
      .then(r => r.json())
      .then(data => setUsers(data));
  }, [options]);

  return <UsersTable users={users} />;
}

Alguns códigos tornam isso pior ao “normalizar” a resposta em um array completamente novo toda vez, então ele chama setUsers mesmo quando nada mudou.

A correção

Estabilize as entradas do efeito, evite atualizações desnecessárias e cancele requisições em andamento.

function AdminUsers({ orgId }) {
  const [users, setUsers] = React.useState([]);

  const options = React.useMemo(
    () => ({ method: "GET", headers: { "x-org": orgId } }),
    [orgId]
  );

  React.useEffect(() => {
    const controller = new AbortController();

    fetch("/api/users", { ...options, signal: controller.signal })
      .then(r => r.json())
      .then(data => {
        setUsers(prev => (sameUsers(prev, data) ? prev : data));
      })
      .catch(err => {
        if (err.name !== "AbortError") throw err;
      });

    return () => controller.abort();
  }, [options]);

  return <UsersTable users={users} />;
}

Para verificar que funcionou:

  • Adicione um contador de renders e confirme que ele para de subir
  • Observe a aba Network: requisições repetidas devem parar
  • Mude orgId uma vez e confirme que você recebe exatamente um novo fetch

Um pequeno refactor ajuda a evitar o retorno do bug: extraia o fetch para um hook useUsers(orgId), nomeie valores memoizados claramente e mantenha dependências de efeito curtas e estáveis.

Próximos passos se o código ainda entrar em loop

Se você corrigiu o problema óbvio (como um array de dependências faltando) e a aplicação ainda gira, assuma que há mais de um gatilho. Muitos loops são uma cadeia: uma atualização de estado dispara um efeito, esse efeito atualiza outra coisa e outro componente reage reescrevendo o primeiro estado.

Uma correção pequena basta quando você consegue apontar uma causa clara, como um efeito que seta estado a cada execução ou um callback de prop que muda identidade a cada render.

Uma reescrita muitas vezes é a melhor escolha quando um componente está fazendo demais: fetching, ordenação, filtragem, estado de formulário e estado de UI tudo misturado. Se você continua adicionando flags de “rodar só uma vez”, está tratando sintomas.

Se você herdou uma base de código React gerada por IA que não se estabiliza, FixMyMess (fixmymess.ai) pode ajudar traçando a cadeia de gatilhos e reparando o fluxo subjacente de estado e efeitos, não apenas adicionando proteções. Uma auditoria de código gratuita frequentemente é suficiente para apontar o loop exato e a correção segura mais rápida.