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:

  1. Antes de re-executar o efeito (caso alguma dependência do array tenha mudado).
  2. 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

  1. 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.
  2. Usar useEffect para 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.
  3. Condição de Corrida (Race Conditions) em Chamadas de API: Fazer requisições HTTP dentro de useEffect sem cancelamento com AbortController pode 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 useEffect para 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 AbortController Nativo: Em requisições fetch dentro de efeitos, passe sempre o signal do 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.