6 Técnicas Secretas do useEffect
Além do fetch e do event listener. O que a documentação mostra — e o que ela não explica direito.

O useEffect é o hook mais ensinado. E o mais mal entendido. Não porque a API seja ruim — mas porque a maioria dos tutoriais para no segundo exemplo.
Fetch na montagem. Listener de evento. Array de dependências com a variável certa. Isso é o básico, e o básico está em todo lugar.
O que não está em todo lugar são os seis comportamentos que separam quem usa o useEffect de quem entende o useEffect.
Técnica 01 — Cleanup com AbortController
Toda vez que um componente desmonta antes do fetch terminar, a resposta ainda chega — e tenta atualizar o estado de um componente que não existe mais.
O React lançava o aviso no console: "Can't perform a React state update on an unmounted component". O aviso foi removido no React 18. O bug continua.
function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { const controller = new AbortController(); async function fetchUser() { try { const res = await fetch(`/api/users/${userId}`, { signal: controller.signal, }); const data = await res.json(); setUser(data); } catch (err) { if (err.name === 'AbortError') return; console.error(err); } } fetchUser(); return () => controller.abort(); }, [userId]); return <div>{user?.name}</div>;}Técnica 02 — O array de dependências é uma declaração, não um filtro
O instinto errado aparece assim: o efeito roda demais, então o dev remove uma dependência do array para "parar" as execuções extras. Funciona até o dia que não funciona.
O array de dependências diz ao React quais valores esse efeito usa. Não quais valores devem ativar o efeito. Essa distinção muda tudo. Omitir uma dependência não silencia o re-run — cria um stale closure: o efeito fica preso num snapshot antigo do valor, e o bug aparece de forma não determinística.
useEffect(() => {
// count é lido aqui,
// mas foi omitido do array.
// O valor nunca atualiza.
document.title = Count: ${count};
}, []); // ← mentira pro React
useEffect(() => {
// count está no array.
// React sabe quando re-executar.
document.title = Count: ${count};
}, [count]); // ← contrato honesto
Se o efeito roda demais, o problema real geralmente é um objeto ou função sendo recriado a cada render. A solução é estabilizar os valores — com useCallback, useMemo, ou movendo a definição pra dentro do próprio efeito. Não omitir.

Técnica 03 — useLayoutEffect: quando o DOM não pode esperar
O useEffect dispara depois da pintura do navegador. Na maioria dos casos, é exatamente o que você quer. Mas existe uma janela entre o React comitar as mudanças no DOM e o navegador pintar a tela.
Em posicionamento de tooltips, medições de layout e animações que dependem de tamanho real — o useEffect dispara tarde demais. O usuário vê o elemento na posição errada antes de corrigir.
function Tooltip({ targetRef, children }) { const tooltipRef = useRef(null); const [pos, setPos] = useState({ top: 0, left: 0 }); // Mede ANTES da pintura — sem flash de posição incorreta useLayoutEffect(() => { if (!targetRef.current || !tooltipRef.current) return; const targetRect = targetRef.current.getBoundingClientRect(); const tooltipRect = tooltipRef.current.getBoundingClientRect(); setPos({ top: targetRect.top - tooltipRect.height - 8, left: targetRect.left + targetRect.width / 2 - tooltipRect.width / 2, }); }, [targetRef]); return ( <div ref={tooltipRef} style={{ position: 'fixed', top: pos.top, left: pos.left }}> {children} </div> );}Técnica 04 — Refs como válvula de escape das dependências
Funções de callback chegam como props. Quando você precisa chamá-las dentro de um efeito, o linter exige que entrem no array de dependências. Mas callbacks recriados a cada render fazem o efeito reexecutar sem motivo real.
A ref é a saída: guarda o valor mais recente sem fazer o efeito depender dele.
function useInterval(callback, delay) { // Ref sempre com a versão mais recente do callback const savedCallback = useRef(callback); // Atualiza a ref sem re-executar o efeito useEffect(() => { savedCallback.current = callback; }, [callback]); // O interval usa a ref — array de deps só precisa de [delay] useEffect(() => { if (delay === null) return; const id = setInterval(() => { savedCallback.current(); // lê o valor mais recente via ref }, delay); return () => clearInterval(id); }, [delay]);}Técnica 05 — useEffectEvent: ler estado sem depender dele
Um efeito precisa reagir a uma mudança — roomId — mas dentro dele, precisa ler um valor atual — theme — sem que esse valor cause reexecução. O React 19 introduziu useEffectEvent para formalizar essa separação entre código reativo e não-reativo.
import { experimental_useEffectEvent as useEffectEvent } from 'react';function ChatRoom({ roomId, theme }) { // onConnected lê theme por dentro, mas não depende dele const onConnected = useEffectEvent(() => { showNotification(`Conectado à sala ${roomId}`, theme); }); useEffect(() => { const connection = createConnection(roomId); connection.on('connected', () => onConnected()); connection.connect(); return () => connection.disconnect(); }, [roomId]); // theme não precisa estar aqui}Técnica 06 — useEffect não gerencia estado derivado
Esse é o erro silencioso mais comum. O código funciona. Os testes passam. E ainda assim tem um render a mais toda vez — além de um useEffect que não deveria existir.
// ❌ Effect para derivar estado — causa render duplofunction FilteredList({ items, query }) { const [filtered, setFiltered] = useState([]); useEffect(() => { setFiltered(items.filter(item => item.name.includes(query))); }, [items, query]); return <List items={filtered} />;}// ✅ Calculado no render — correto e sem overheadfunction FilteredList({ items, query }) { const filtered = useMemo( () => items.filter(item => item.name.includes(query)), [items, query] ); return <List items={filtered} />;}useEffect existe para sincronizar o React com sistemas externos: APIs, WebSockets, timers, localStorage, bibliotecas de terceiros. Não para transformar dados que já estão dentro do React.
O useEffect não mudou muito desde o React 16.8. O que mudou é o entendimento do que ele realmente é: uma camada de sincronização entre o React e o mundo externo.
Tudo que não precisa do mundo externo — não pertence a ele.
Teste seu conhecimento
Responda e valide seu entendimento.


