O abuso do useEffect
useEffect é uma das ferramentas mais usadas em excesso do React. Muita
gente o trata como "código que roda quando algo muda", e o enfia por toda
parte. Mas um efeito serve para uma coisa concreta: sincronizar seu
componente com um sistema externo (a rede, o DOM direto, um
setInterval, uma assinatura...). Se você o usa para outra coisa, quase
sempre há uma opção melhor, mais simples e sem bugs.
Antipadrão: estado "derivado" como estado + efeito
Caso clássico: você tem nome e sobrenome no estado e quer o nome
completo. A tentação é um terceiro estado sincronizado com um efeito:
// ❌ Desnecessário: estado redundante + efeito
function Formulario() {
const [nome, setNome] = useState("Ada");
const [sobrenome, setSobrenome] = useState("Lovelace");
const [completo, setCompleto] = useState("");
useEffect(() => {
setCompleto(nome + " " + sobrenome);
}, [nome, sobrenome]);
// ...
}
Isto é pior em todos os sentidos: há um estado a mais, um render extra
(o efeito roda depois de pintar, e setCompleto provoca outro render)
e um momento em que completo fica dessincronizado.
A regra: se você pode calculá-lo a partir de props/estado, faça-o na renderização
O nome completo é estado derivado: não é informação nova, se deriva do que você já tem. Calcule-o durante a renderização, como uma variável normal:
// ✅ Derivado durante a renderização
function Formulario() {
const [nome, setNome] = useState("Ada");
const [sobrenome, setSobrenome] = useState("Lovelace");
const completo = nome + " " + sobrenome; // recalculado a cada render
// ...
}
Sem efeito, sem estado extra, sem dessincronização. Isto vale para filtrar uma
lista, contar elementos, formatar um valor, saber se algo está
vazio... tudo isso se calcula na renderização. (Se o cálculo for realmente
caro, envolva-o em useMemo para não repeti-lo a cada render; mas useMemo é só
uma otimização, não uma mudança de abordagem.)
Antipadrão: lógica de evento dentro de um efeito
Outro mau uso: executar lógica que deveria ir em um manipulador de eventos.
// ❌ Reagir a uma mudança de estado com um efeito para "fazer algo"
useEffect(() => {
if (enviado) {
mostrarToast("Compra realizada!");
esvaziarCarrinho();
}
}, [enviado]);
Essa lógica acontece porque o usuário clicou em "Comprar", não porque o componente se sincroniza com um sistema externo. Seu lugar é o handler:
// ✅ A lógica da interação vai no manipulador do evento
function comprar() {
enviarPedido();
mostrarToast("Compra realizada!");
esvaziarCarrinho();
}
Regra prática: se algo acontece porque o usuário fez algo (um clique, um submit), vai no manipulador de eventos. Se algo acontece porque o componente apareceu na tela e deve se sincronizar com algo externo, vai em um efeito.
Quando você REALMENTE precisa de um efeito?
Um efeito é a ferramenta correta para sincronizar com sistemas externos ao React:
- Inscrever-se em uma fonte de dados (um WebSocket, um
storeexterno, um evento do navegador) e cancelar a inscrição ao desmontar. - Controlar manualmente uma API do navegador ou do DOM (medir um nó,
focar um input, iniciar/parar um
setInterval). - Disparar requisições de rede que devem ocorrer ao aparecer o componente (embora para isso, o ideal seja uma biblioteca de data fetching como TanStack Query, que envolve o efeito para você).
// ✅ Sincronizar com um sistema externo: com limpeza
useEffect(() => {
const id = setInterval(() => setSegundos((s) => s + 1), 1000);
return () => clearInterval(id); // limpeza ao desmontar
}, []);
Antes de escrever um
useEffect, pergunte-se: estou sincronizando com algo externo, ou só tento calcular um valor ou responder a uma interação? Nos dois últimos casos, You Might Not Need an Effect.