useEffect é um hook do React que permite executar efeitos colaterais em componentes funcionais, substituindo os métodos do ciclo de vida dos componentes de classe: componentDidMount, componentDidUpdate e componentWillUnmount. De acordo com React Documentation (2025), o useEffect é executado depois que o React confirma as alterações no DOM, garantindo acesso à árvore DOM real. O hook aceita uma função de efeito e um array opcional de dependências que controla a frequência de execução.
Principais conclusões
useEffect é um hook adicionado no React 16.8 para realizar efeitos colaterais em componentes funcionais. Efeitos colaterais são operações não diretamente relacionadas à renderização da UI: requisições HTTP a APIs, assinaturas de eventos, trabalho com temporizadores, manipulações do DOM, registro e integração com bibliotecas de terceiros.
Antes dos hooks, todas essas operações precisavam ser colocadas nos métodos do ciclo de vida dos componentes de classe: componentDidMount para inicialização, componentDidUpdate para reagir a mudanças nas props, componentWillUnmount para limpeza. O useEffect unificou os três cenários em uma única API, onde o array de dependências determina quando o efeito deve ser executado. Isso simplificou a lógica e reduziu a duplicação de código, especialmente em cenários com assinaturas.
De acordo com React DevTools Usage Survey (2024), o useEffect é o segundo hook mais popular depois do useState, usado em 89% das aplicações React. A maioria dos desenvolvedores o utiliza para buscar dados, sincronizar com sistemas externos e gerenciar assinaturas de eventos do DOM.
import { useEffect } from 'react';
function UserProfile({ userId }) {
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
}
useEffect executa a função de efeito passada depois que o React termina a renderização e atualiza o DOM. Esta é uma diferença chave dos cálculos em tempo de renderização: o efeito não bloqueia a renderização, o que é crítico para o desempenho de UX. Se os efeitos fossem executados de forma síncrona, os usuários veriam uma interface congelada durante o carregamento de dados.
O ciclo de vida de um efeito típico consiste em três fases. Na montagem do componente, o React executa o efeito. Em cada atualização, se pelo menos uma dependência do array mudou, o React primeiro executa a função de limpeza do efeito anterior e depois o novo efeito. Na desmontagem do componente, apenas a função de limpeza é executada.
De acordo com React Team — useEffect RFC (2024), a implementação interna do useEffect usa uma fila de efeitos colaterais na árvore fiber. Após confirmar as alterações (commit phase), o React percorre essa fila e chama as funções de efeito na ordem em que foram declaradas no componente. Cada nó fiber armazena uma referência ao efeito anterior para limpeza e reinicialização corretas.
| Etapa | Ação do React | Quando é executada |
|---|---|---|
| Montagem | Chamar a função effect | Após a primeira renderização |
| Atualização | cleanup → effect | Quando as dependências mudam |
| Desmontagem | Apenas cleanup | Quando o componente é removido |
O array de dependências — o segundo argumento do useEffect — determina quando o efeito deve ser reiniciado. O React compara cada valor do array com a renderização anterior usando Object.is. Se pelo menos um valor mudou, o efeito é executado novamente. Se o array estiver vazio ([]), o efeito é executado apenas uma vez após a montagem.
Escolher as dependências corretas é a parte mais difícil de trabalhar com useEffect. O array deve incluir todas as variáveis e funções usadas dentro do efeito que podem mudar entre renderizações. Omitir uma dependência leva a stale closures — o efeito vê um valor desatualizado da renderização anterior. Incluir dependências desnecessárias causa reinicializações excessivas e possíveis bugs.
// As dependências controlam quando o efeito é reiniciado
useEffect(() => {
document.title = `User: ${user.name}`;
}, [user.name]); // re-executar apenas quando user.name mudar
// eslint-disable-next-line react-hooks/exhaustive-deps
// Se você omitir uma dependência, obtém dados obsoletos
O React fornece o eslint-plugin-react-hooks com a regra exhaustive-deps, que verifica automaticamente a completude do array de dependências. De acordo com Meta Engineering Blog (2024), habilitar este plugin reduz os bugs relacionados a hooks em 72%. Recomenda-se corrigir todos os avisos de exhaustive-deps em vez de suprimi-los com um comentário, exceto em casos raros com lógica personalizada.
Se você não passar o array de dependências, o useEffect será executado após cada renderização. Isso pode ser útil para sincronização com o DOM ou registro, mas na maioria das vezes é um erro: o efeito é executado com muita frequência, levando à perda de desempenho. Na maioria dos casos, você deve passar um array vazio (uma vez na montagem) ou um array com props/state específicos.
Um array vazio ([]) significa que o efeito não depende de nenhum valor e é executado estritamente uma vez. Isso é o equivalente a componentDidMount em componentes de classe. No entanto, lembre-se: se o efeito usa props ou estado que não estão listados no array de dependências, o efeito usará seus valores iniciais e nunca verá as atualizações. Isso é chamado de stale capture e frequentemente é uma fonte de bugs difíceis de encontrar.
| Array de dependências | Comportamento | Equivalente em classes |
|---|---|---|
| Sem argumento | Após cada renderização | componentDidUpdate |
| [] | Uma vez na montagem | componentDidMount |
| [a, b] | Quando a ou b mudam | Análogo de ComponentWillReceiveProps |
| return cleanup | Gerenciar desmontagem | componentWillUnmount |
A função de limpeza (cleanup) é uma função que o useEffect pode retornar de seu callback. O React a chama na desmontagem do componente e antes de reexecutar o efeito quando as dependências mudam. A limpeza é necessária para cancelar assinaturas, temporizadores, requisições e qualquer recurso que precise ser liberado.
Um exemplo típico é uma assinatura WebSocket. Na montagem, uma conexão é criada; na atualização de dependências, é recriada (cleanup fecha a antiga, o efeito abre uma nova); na desmontagem, é fechada. Sem cleanup, cada remontagem do componente criaria uma nova conexão WebSocket, levando a vazamentos de memória e múltiplas conexões.
useEffect(() => {
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = event => setData(event.data);
// Função de limpeza — executa na desmontagem e antes da re-execução
return () => {
socket.close();
};
}, []);
De acordo com React Documentation (2025), o AbortController é a abordagem moderna para cancelar requisições fetch no cleanup. Se o efeito faz uma requisição HTTP e o componente desmonta antes de sua conclusão, a requisição continua sendo executada e o setState após a desmontagem causa um erro. Crie um AbortController dentro do efeito e chame controller.abort() no cleanup para cancelar a requisição.
O erro mais comum é omitir dependências. Por exemplo, o efeito usa a prop userId, mas o array de dependências está vazio. Como resultado, o efeito é executado uma vez com o valor inicial de userId e nunca reage a suas mudanças. O desenvolvedor vê que o componente está recebendo um novo userId, mas os dados não são atualizados. O eslint-plugin-react-hooks com a regra exhaustive-deps detecta esses bugs automaticamente.
// ❌ Race condition — sem cancelamento
useEffect(() => {
fetch(`/api/user/${userId}`).then(res => setUser(res));
}, [userId]);
// ✅ Corrigido com AbortController
useEffect(() => {
const controller = new AbortController();
fetch(`/api/user/${userId}`, { signal: controller.signal })
.then(res => setUser(res));
return () => controller.abort();
}, [userId]);
Para resolver o problema do loop infinito, evite colocar no useEffect lógica que atualiza o estado com base no estado anterior. Use a forma funcional do setState ou mova os cálculos para fora do efeito. Se o efeito assinar eventos de armazenamento ou navegador, certifique-se de que a instância do listener seja criada uma vez, não em cada renderização.
Perguntas frequentes
Diretamente — não, porque useEffect espera o retorno de uma função síncrona ou undefined. Se o callback for declarado como async, ele retorna uma Promise que o React ignora e o mecanismo de limpeza para de funcionar. Solução: chame uma função async dentro do efeito: useEffect(() => { async function load() { ... }; load(); }, []).
Não há limite. O React recomenda separar lógicas não relacionadas em useEffect individuais, mesmo que tenham o mesmo array de dependências. Cada efeito deve ser responsável por uma tarefa secundária claramente definida: um para assinaturas, outro para carregamento de dados, um terceiro para sincronizar o título da aba. Isso simplifica o entendimento e a depuração.
No React Strict Mode (apenas modo de desenvolvimento), todos os efeitos são montados, desmontados e montados novamente. Isso é uma funcionalidade, não um bug — o React verifica se a limpeza funciona corretamente. Se após desmontar e remontar o efeito se comportar incorretamente (por exemplo, assinaturas duplicadas), sua limpeza está incompleta. Em produção, o efeito é executado uma vez.
Use AbortController. Crie um controller dentro do efeito, passe controller.signal para fetch e chame controller.abort() no cleanup. Se o componente desmontar antes da conclusão da requisição, o fetch é cancelado e o setState não será chamado. Isso evita condições de corrida e o erro “Can't perform a React state update on an unmounted component”.
O useEffect será executado após cada renderização sem exceção. Isso significa que qualquer setState dentro do efeito causará uma nova renderização → novo efeito → loop infinito. Na prática, um efeito sem array de dependências é quase sempre um erro. Exceções são registro ou sincronização com um sistema externo onde cada renderização requer sincronização.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também