Capítulo 04: Efeitos Colaterais com useEffect, useLayoutEffect e Cleanup
Especialização Frontend com React & TypeScript • React 19, Hooks, Estado Global e Next.js
🗺️ Mapa Conceitual do Tópico
flowchart TD
MOUNT["Componente Montado na Tela"] --> SETUP["Executa callback do useEffect (Setup)"]
SETUP --> RUN["Efeito Ativo (Timers, WebSockets, Listeners)"]
RUN --> CHANGE{"Array de Dependências alterou?"}
CHANGE -- "Sim" --> CLEANUP_OLD["Executa função de Cleanup anterior"]
CLEANUP_OLD --> SETUP_NEW["Executa novo callback do useEffect"]
CHANGE -- "Não (ou desmontagem)" --> UNMOUNT["Desmontagem: Executa Cleanup final"]
style MOUNT fill:#e1f5fe,stroke:#03a9f4,stroke-width:1.5px
style SETUP fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style CLEANUP_OLD fill:#ffebee,stroke:#d32f2f,stroke-width:2px
style UNMOUNT fill:#ffebee,stroke:#d32f2f,stroke-width:2px
🏛️ 1. O Papel dos Efeitos Colaterais no React
O Hook useEffect serve como uma ponte (Escape Hatch) para conectar o mundo declarativo dos componentes React a sistemas externos que não são controlados pelo React (APIs de rede, WebSockets, manipulação de títulos de página e timers do navegador).
🔄 2. A Função de Limpeza (Cleanup) e Prevenção de Vazamento de Memória
Sempre que um efeito aloca recursos contínuos (como um setInterval, uma inscrição de eventos com addEventListener ou uma conexão WebSocket), ele deve retornar uma função de cleanup. O React executa o cleanup:
- Antes de re-executar o efeito (caso alguma dependência do array tenha mudado).
- No momento em que o componente é desmontado da tela.
⚖️ 3. useEffect vs useLayoutEffect
| Característica | useEffect (Padrão) |
useLayoutEffect (Casos Especiais) |
|---|---|---|
| Momento de Execução | Assíncrono, após a renderização e após o navegador pintar a tela. | Síncrono, imediatamente após as mutações do DOM e antes da pintura na tela. |
| Impacto na UI | Não bloqueia a taxa de quadros (60/120 FPS). Ideal para fetch de dados. | Pode travar a renderização visual se o cálculo for longo. |
| Caso de Uso Exclusivo | Redes, timers, telemetria, logs. | Medição precisa de elementos no DOM (getBoundingClientRect) para posicionar tooltips/modais sem oscilação visual (flicker). |
⚠️ 4. Armadilhas Comuns e Anti-Padrões de Produção
- Omitir Dependências no Array: Tentar contornar avisos do linter omitindo variáveis usadas dentro do efeito cria fechamentos estáticos (stale closures) que consomem valores antigos de estado.
- Usar
useEffectpara Transformação Pura de Dados: Recorrer a um efeito para sincronizar dois estados locais é um dos piores anti-padrões do React. Elimine um re-render calculando diretamente durante a renderização. - Condição de Corrida (Race Conditions) em Chamadas de API: Fazer requisições HTTP dentro de
useEffectsem cancelamento comAbortControllerpode fazer uma resposta mais lenta sobrescrever uma busca mais recente digitada pelo usuário.
💡 5. Diretrizes de Engenharia (Meta & React Docs)
- Você Provavelmente Não Precisa de um Efeito: Consulte a documentação oficial da Meta “You Might Not Need an Effect”. Só utilize
useEffectpara sincronização com sistemas externos, nunca para orquestrar fluxos de eventos iniciados pelo usuário (como cliques em botões, que devem residir em handlers). - Adote
AbortControllerNativo: Em requisiçõesfetchdentro de efeitos, passe sempre osignaldo controlador e invoque.abort()no cleanup.
💻 6. Demonstração Arquitetural do Conceito
// WindowResizeTracker.tsx - Efeito com listener e cleanup
import React, { useState, useEffect } from "react";
export function WindowResizeTracker() {
const [dimensions, setDimensions] = useState({
width: typeof window !== "undefined" ? window.innerWidth : 0,
height: typeof window !== "undefined" ? window.innerHeight : 0
});
useEffect(() => {
const handleResize = () => {
setDimensions({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return (
<div className="dimension-badge">
Janela: {dimensions.width}px x {dimensions.height}px
</div>
);
}
📋 7. Cheat-Sheet do Array de Dependências do useEffect
| Sintaxe do Array | Quando Executa? | Caso de Uso Típico |
|---|---|---|
useEffect(fn) (sem array) |
A cada ciclo de renderização do componente. | Debugging intensivo ou medições universais (raro em produção). |
useEffect(fn, []) (array vazio) |
Uma única vez, na montagem do componente. | Inicialização de SDKs globais, timers permanentes ou listeners globais. |
useEffect(fn, [id, status]) |
Na montagem e sempre que id ou status mudarem de valor. |
Sincronização de recursos externos com parâmetros dinâmicos da rota/props. |
🔍 8. Diagnóstico de Performance & React DevTools Profiler
Na auditoria de efeitos colaterais:
- Efeitos em Loop Infinito: Se o profiler registrar centenas de commits por segundo com a mensagem
"effect triggered state update", verifique se o array de dependências contém um objeto recriado a cada render. - Vazamento de Listeners: Utilize o comando
getEventListeners(window)no console do Chrome DevTools para confirmar que a função de cleanup removeu os listeners de resize/scroll na desmontagem.
🧪 9. Casos de Teste e Cenários de Borda Recomendados
- 🔍 Desmontagem Antes da Resposta HTTP: Garanta que o
AbortController.abort()cancele requisições ativas sem emitir alertas de memory leak no console. - 🔍 Mudança Rápida de IDs: Alterne props de ID rapidamente para certificar-se de que a resposta mais antiga não sobrescreva a mais nova (Race Condition).
- 🔍 Pausa e Retomada de Timers: Teste a estabilidade de intervalos quando a aba do navegador perde o foco.