🌐 Detecting your location…

Como corrigir o aviso ‘React Hook useEffect tem uma dependência ausente’

⏱️7 min read  ·  1,345 words

React Hook useEffect has a missing dependency: 'x'. Either include it or remove the dependency array.A maioria dos desenvolvedores silencia isso com um comentário sobre desativação do eslint. Isso funciona até causar um bug de dados obsoletos que leva um dia para ser encontrado. O aviso geralmente está correto e existe uma solução certa para cada situação.

Por que existe o aviso

O array de dependência informa ao React quando executar novamente um efeito. Se o efeito ler um valor que não está listado, o efeito mantém o valor da renderização em que foi executado pela última vez — um fechamento obsoleto. O código parece correto e se comporta incorretamente.

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(setUser);
  }, []);   // ⚠️ missing dependency: 'userId'

  return <div>{user?.name}</div>;
}

Com um array vazio, a busca é executada uma vez. Navegue do usuário 1 para o usuário 2 e o componente continuará mostrando o usuário 1 para sempre. O aviso previu exatamente isso.

useEffect(() => {
  fetch(`/api/users/${userId}`)
    .then(r => r.json())
    .then(setUser);
}, [userId]);   // ✅ refetches whenever userId changes

Correção 1: basta adicionar a dependência

Na maioria dos casos, o aviso está correto e adicionar a dependência é a solução completa. Faça isso primeiro e só investigue mais se causar um loop.

Correção 2: o efeito é repetido após adicioná-lo

Adicionar um objeto, array ou função ao array de dependência geralmente causa um loop infinito, porque eles são recriados em cada renderização e comparados por referência.

function Search({ filters }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    search(filters).then(setResults);
  }, [filters]);   // new object identity every render -> infinite loop
}

Depende dos valores primitivos em vez do objeto.

useEffect(() => {
  search({ query, category }).then(setResults);
}, [query, category]);   // strings compare by value — stable

Quando o objeto vier genuinamente de um dos pais, registre-o ali.

// Parent
const filters = useMemo(
  () => ({ query, category }),
  [query, category]
);
return <Search filters={filters} />;

Correção 3: Dependências de Função – useCallback

Uma função declarada no corpo do componente é um novo valor a cada renderização, portanto, listá-la executa novamente o efeito indefinidamente.

function Dashboard({ userId }) {
  // Recreated every render.
  const loadData = async () => {
    const res = await fetch(`/api/data/${userId}`);
    return res.json();
  };

  useEffect(() => {
    loadData().then(setData);
  }, [loadData]);   // loops
}

Duas correções corretas. Se a função for usada apenas pelo efeito, mova-a para dentro — a opção mais limpa, pois a dependência desaparece completamente.

useEffect(() => {
  async function loadData() {
    const res = await fetch(`/api/data/${userId}`);
    setData(await res.json());
  }
  loadData();
}, [userId]);   // ✅ only the primitive is a dependency

Se a função for compartilhada com outras partes do componente, envolva-a emuseCallback.

const loadData = useCallback(async () => {
  const res = await fetch(`/api/data/${userId}`);
  return res.json();
}, [userId]);

useEffect(() => {
  loadData().then(setData);
}, [loadData]);   // ✅ identity only changes when userId changes

Correção 4: você precisa do valor mais recente sem executar novamente

Às vezes, um efeito deveria ser executado legitimamente uma vez, mas o retorno de chamada interno precisa de valores atuais. Uma ref contém um valor mutável que não participa de dependências.

function Chat({ onMessage }) {
  const onMessageRef = useRef(onMessage);

  // Keep the ref current on every render.
  useEffect(() => {
    onMessageRef.current = onMessage;
  });

  useEffect(() => {
    const socket = new WebSocket('wss://example.com');
    socket.onmessage = (e) => onMessageRef.current(e.data);
    return () => socket.close();
  }, []);   // ✅ connects once, always calls the latest handler
}

Este é o padrão correto para assinaturas, temporizadores e ouvintes de eventos, onde a reconexão a cada alteração de prop seria errada.

Correção 5: Atualizando o estado a partir de seu próprio valor anterior

Ler o estado para calcular o próximo estado torna esse estado uma dependência, o que geralmente causa um loop. O formulário atualizador remove a dependência.

// Loops: count changes -> effect re-runs -> count changes...
useEffect(() => {
  const id = setInterval(() => setCount(count + 1), 1000);
  return () => clearInterval(id);
}, [count]);

// ✅ No dependency on count at all.
useEffect(() => {
  const id = setInterval(() => setCount(c => c + 1), 1000);
  return () => clearInterval(id);
}, []);

Ao desabilitar a regra está realmente correta

Existe um caso legítimo: um verdadeiro efeito somente de montagem onde você deseja deliberadamente o valor inicial e nada mais. A análise na primeira renderização é o exemplo padrão.

useEffect(() => {
  analytics.track('page_view', { page: pageName });
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);   // Intentional: fire once with the initial pageName.

Se você desabilitar a regra, escreva um comentário explicando por que a omissão é deliberada. Um simples comentário sobre desativação é indistinguível de alguém que não entendeu o aviso, inclusive para você mesmo em seis meses.

Você ainda precisa de um efeito?

Uma grande parte dos avisos de profundidade exaustiva vem de efeitos que não deveriam existir. Dois casos comuns:

Estado derivado. Calcular um valor a partir de adereços ou estado não precisa de efeito.

// Unnecessary effect, extra render, and a dependency warning.
const [fullName, setFullName] = useState('');
useEffect(() => { setFullName(`${first} ${last}`); }, [first, last]);

// ✅ Just calculate it during render.
const fullName = `${first} ${last}`;

Respondendo a um evento. A lógica que pertence a um manipulador de eventos geralmente termina em um estado de observação de efeitos definido pelo manipulador.

// ✅ Do the work where the event happens.
function handleSubmit() {
  submitForm(data);
  analytics.track('form_submitted');
}

Os efeitos são para sincronização com sistemas fora do React – rede, assinaturas, DOM, temporizadores. Se nada externo estiver envolvido, um efeito provavelmente é a ferramenta errada.

Guia de decisão

Situação Correção
Aviso nomeia um primitivo Adicione-o à matriz
Objeto ou array causa um loop Depende de seus campos primitivos, ouuseMemo na fonte
Função causa um loop Mova-o para dentro do efeito ouuseCallback
Precisa do valor mais recente, não deve executar novamente Armazene-o em uma referência
O estado deriva do estado anterior Use o formulário da função atualizadora
O valor é calculado a partir de adereços ou estado Exclua o efeito e calcule durante a renderização
Somente montagem genuinamente Desative a regra com uma justificativa por escrito

Perguntas Frequentes

P: Por que meu efeito é executado duas vezes durante o desenvolvimento?
R: O Modo Estrito monta, desmonta e remonta componentes intencionalmente para revelar funções de limpeza ausentes. Isso não acontece na produção. Se a execução dupla quebrar alguma coisa, seu efeito estará faltando uma limpeza.

P: Posso simplesmente desativar a regra de dependências exaustivas?
R: Você pode, e enviará bugs de fechamento obsoletos que são extremamente difíceis de rastrear. A regra detecta defeitos reais; mantenha-o ligado e corrija as causas.

P: UseCallback em todos os lugares prejudica o desempenho?
R: Um pouco – custa memória e trabalho de comparação. Use-o onde a estabilidade da identidade realmente importa, como matrizes de dependência e filhos memorizados, e não reflexivamente em todas as funções.

P: E quanto ao compilador React?
A: Ele automatiza grande parte da memorização queuseCallback euseMemo faça manualmente, o que remove uma categoria desses avisos. Isso não elimina a necessidade de entender de quais valores seu efeito realmente depende.

P: Meu efeito precisa de um valor, mas não deve ser executado novamente quando for alterado. Um árbitro é um hack?
R: Não, é o padrão documentado para essa situação. Manter a ref atualizada em um efeito separado e leitura.current dentro do efeito de longa duração é idiomático.

Conclusão

O aviso de profundidade exaustiva é um detector de bugs, não um incômodo. Adicione a dependência primeiro; se isso fizer um loop, corrija o problema de identidade com primitivos,useMemo, ouuseCallback; use uma referência quando precisar de valores atuais sem executar novamente; use o formulário atualizador de estado para quebrar loops autorreferenciais; e exclua totalmente o efeito quando o valor for meramente derivado. Reserve eslint-disable para efeitos genuínos apenas de montagem e sempre deixe um comentário dizendo o porquê.

MD Rafikul Islam

Written by

MD Rafikul Islam is a software developer and the editor of TechPulse. He writes about developer tooling, hardware, and the practical decisions that come up in day-to-day engineering work — which laptop to buy, which framework to commit to, why a build broke at 2am. He tests the tools he writes about and says plainly when something is not worth the money. Corrections and corrections requests are welcome at rony.yf25@gmail.com.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা