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.
📋 Table of Contents
- Por que existe o aviso
- Correção 1: basta adicionar a dependência
- Correção 2: o efeito é repetido após adicioná-lo
- Correção 3: Dependências de Função – useCallback
- Correção 4: você precisa do valor mais recente sem executar novamente
- Correção 5: Atualizando o estado a partir de seu próprio valor anterior
- Ao desabilitar a regra está realmente correta
- Você ainda precisa de um efeito?
- Guia de decisão
- Perguntas Frequentes
- Conclusã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ê.
🔗 Share this article
✍️ Leave a Comment