O que é um DDoS acidental por bug de renderização
Imagine acordar com o monitoramento gritando que o banco de dados caiu. Não foi um ataque externo, não foi um pico de usuários. Foi um único componente React com um colchete no lugar errado disparando 100.000 requisições em minutos.
Esse tipo de incidente tem um nome carinhoso na comunidade: DDoS acidental. O desenvolvedor literalmente causa uma negação de serviço no próprio sistema sem querer, só com um erro sutilissimo de código.
O problema e mais comum do que parece. React re-renderiza componentes em resposta a mudanças de estado e props. Quando uma dependência em um useEffect ou useMemo e declarada errado, especialmente usando um array ou objeto como dependência, o React entende que algo sempre mudou - e dispara o efeito novamente a cada render. Se esse efeito faz uma chamada de API, você acabou de criar um loop infinito de requisições.
Como funciona o bug
O cenário clássico envolve o hook useEffect com uma dependência que parece idêntica a cada render, mas que tecnicamente e um objeto diferente na memoria. No JavaScript, [] === [] retorna false. Dois arrays com o mesmo conteúdo não são o mesmo array.
Quando você coloca um array literal ou objeto literal dentro das dependências do useEffect, React compara por referência, não por valor. Como o JavaScript cria um objeto novo a cada render, React sempre ve uma dependência diferente, dispara o efeito, atualiza o estado, re-renderiza - e o ciclo recomeça.
Nunca coloque um objeto ou array literal diretamente nas dependências do useEffect sem memoizar antes. useEffect(() => fetchData(), [{ id: 1 }]) vai explodir em produção.
O artigo que viralizou no Dev.to descreveu exatamente isso: o desenvolvedor colocou um objeto de filtros diretamente como dependência. O objeto era recriado a cada render, o React entendia como mudança nova, e a API era chamada novamente - ad infinitum, até o banco desistir.
Principais formas desse bug acontecer
Não e só com objetos literais. Existem vários padrões que causam o mesmo problema:
- Funções inline nas dependências: passar uma função definida dentro do componente sem usar
useCallbackfaz ela ser recriada a cada render - Objetos derivados de props: transformar uma prop em objeto dentro do componente antes de usar como dependência
- Arrays de IDs construidos na hora:
[...ids, novoId]cria array novo mesmo que o conteúdo seja igual - Date ou Math.random() nas dependências: valores que nunca são iguais a si mesmos entre renders
- Refs de contexto não memoizados: quando o provider do contexto recria o valor a cada render do pai
Esse bug quase nunca aparece em desenvolvimento local com dados mínimos. Ele explode em produção quando o componente renderiza frequentemente, em listas, ou quando o contexto pai muda muito.
Como começar a identificar: ferramentas e técnicas
O primeiro passo e perceber que algo esta errado antes que o banco caia. Algumas técnicas práticas:
Passo 1: Instale o React DevTools e abra o Profiler. Renderizações em loop aparecem como barras se acumulando continuamente mesmo sem interação do usuário.
Passo 2: Adicione um log temporário no useEffect suspeito e conte quantas vezes aparece no console em 5 segundos. Mais de 10 já e sinal vermelho.
useEffect(() => {
console.log('useEffect disparado:', new Date().toISOString());
fetchData(filtros);
}, [filtros]); // filtros e um objeto novo a cada render?Passo 3: Use a extensão why-did-you-render no ambiente de desenvolvimento. Ela mostra exatamente quais props ou estados mudaram e causaram o re-render, com diff visual.
Passo 4: Monitore o número de requisições no Network tab do browser. Se subir continuamente sem interação do usuário, você encontrou o problema.
Exemplo prático: o bug e a correção
Veja o padrão clássico que causou o incidente viral:
// BUG: filters e recriado a cada render do componente pai
function DataTable({ userId, status }) {
const filters = { userId, status }; // novo objeto a cada render!
useEffect(() => {
fetchTableData(filters); // dispara SEMPRE
}, [filters]); // React ve objeto diferente a cada vez
return ...
;
}A correção mais simples e usar useMemo para memoizar o objeto de filtros, garantindo que só muda quando as dependências reais (userId e status) mudam:
// CORRIGIDO: filters só muda quando userId ou status mudam
function DataTable({ userId, status }) {
const filters = useMemo(
() => ({ userId, status }),
[userId, status] // dependências reais e primitivas
);
useEffect(() => {
fetchTableData(filters); // só dispara quando filtros realmente mudam
}, [filters]);
return ...
;
}Outra abordagem e eliminar o objeto intermediário e colocar as dependências primitivas diretamente no useEffect:
// ALTERNATIVA: sem objeto intermediário
useEffect(() => {
fetchTableData({ userId, status });
}, [userId, status]); // primitivos são comparados por valorPrefira dependências primitivas (string, number, boolean) sempre que possível. Quando precisar de objeto ou array, memoize com useMemo ou useCallback antes de usar como dependência.
Comparação com alternativas e padrões relacionados
O problema e específico do modelo de renderização do React com hooks, mas outros frameworks tem armadilhas similares:
Vue 3 (Composition API): O watch e watchEffect tem armadilhas parecidas com objetos reativos. A diferença e que o Vue usa Proxy para rastrear mudanças por valor, não por referência - o que elimina esse bug específico, mas cria outros com objetos aninhados.
Angular: A detecção de mudanças do Angular e mais explicita. Com ChangeDetectionStrategy.OnPush, você controla quando o componente re-renderiza, mas tem que gerenciar isso manualmente.
Svelte: A reatividade e baseada em atribuição de variáveis no script, não em comparação de referências - o que torna esse tipo de bug impossível por design.
Para React, o padrão mais seguro e usar bibliotecas como SWR ou React Query para fetching de dados, que já lidam com deduplicação de requisições e cache automaticamente.
Pontos positivos e limitações do useEffect para fetching
O useEffect foi projetado para sincronizar o componente com sistemas externos. Ele funciona bem para casos simples, mas tem limitações importantes quando usado para fetching de dados:
- Ponto positivo: flexibilidade total - você controla exatamente quando e como os dados são buscados
- Ponto positivo: fácil de integrar com qualquer biblioteca HTTP (axios, fetch nativo, etc.)
- Limitação: não tem deduplicação nativa - duas instâncias do mesmo componente fazem duas requisições idênticas
- Limitação: gerenciar loading, erro e dados manualmente e verboso e propenso a bugs
- Limitação: sem cache automático - toda montagem do componente dispara uma nova requisição
Em 2026, o time do React recomenda oficialmente usar frameworks (Next.js, Remix) ou bibliotecas de fetching (React Query, SWR) em vez de useEffect puro para busca de dados. O useEffect puro para fetching e considerado um anti-pattern para a maioria dos casos.
Casos de uso reais: quem mais sofre com isso
Times de frontend juniores: A curva de aprendizado dos hooks do React tem esse tipo de armadilha bem no meio. E um erro clássico de quem esta aprendendo a usar useEffect pela primeira vez em produção.
Dashboards com muitos filtros: Painéis administrativos com filtros encadeados - data, status, usuário, categoria - são o ambiente perfeito para esse bug. Cada filtro e um objeto, cada objeto e uma dependência, e o componente fica em loop.
Listas infinitas e paginação: Componentes que carregam mais dados ao rolar (infinite scroll) são vulneráveis quando o objeto de parâmetros de paginação e recriado a cada render do scroll handler.
Integração com autenticação: Quando o token do usuário ou o objeto de usuário vem de um contexto não memoizado, qualquer componente que usa esse dado como dependência vai re-disparar em loop junto com cada refresh do contexto.
Dicas e boas práticas para nunca mais passar por isso
Antes de adicionar qualquer coisa nas dependências do useEffect, pergunte: isso é um primitivo (string, number, boolean)? Se não for, memoize primeiro.
Ative a regra exhaustive-deps do plugin ESLint para React Hooks. Ela avisa quando as dependências estão erradas ou faltando - e muitas vezes detecta exatamente esse padrão de objeto como dependência.
Independente do frontend, implemente rate limiting por usuário e por endpoint no backend. Mesmo que um bug dispare 100k requisições, o servidor deve rejeitar depois de X por segundo com 429 em vez de aceitar tudo até travar.
Em desenvolvimento com StrictMode ativado, o React monta e desmonta cada componente duas vezes de propósito para detectar efeitos colaterais. Isso pode mascarar loops - você ve 2 requisições e acha normal, mas em produção são infinitas.
Essas bibliotecas resolvem o problema na raiz: deduplicam requisições, tem cache, e a API delas e projetada para não cair nessas armadilhas. Vale o investimento em qualquer projeto serio.
Vale a pena aprender sobre isso?
Com certeza. Esse tipo de bug e um dos mais difíceis de detectar porque não da erro no console, não quebra o layout, e frequentemente só aparece em produção quando o banco já esta sofrendo. E um conhecimento que separa desenvolvedores React medianos de bons.
Se você trabalha com React profissionalmente, o mínimo e conhecer os padrões que causam loops de renderização, saber usar useMemo e useCallback corretamente, e ter o eslint-plugin-react-hooks configurado. Isso já elimina 90% dos casos.
Para o próximo nível, estude a filosofia do React Query: entenda como ele resolve o fetching de dados de forma que esses bugs não existem por design. E uma mudança de mentalidade que vai melhorar muito a qualidade do seu código.
Comentários
Deixar um comentárioVocê precisa ter uma conta no CuritibaBlog para comentar.