useEffect vs. React Query: Performance, Governança de Estado e Escalabilidade Real
Na evolução de aplicações orientadas a dados, eu validei na prática que a diferença entre gerenciar requisições com useEffect e adotar TanStack Query não é cosmética.
Não é “açúcar sintático”. É mudança estrutural na arquitetura de dados, na previsibilidade do front e na UX percebida pelo usuário.
---
1) O problema: quando o useEffect vira camada de dados
useEffect nasceu para efeitos colaterais de ciclo de vida. Quando ele é usado como orquestrador principal de estado remoto, a equipe constrói uma mini-plataforma de data fetching manual em cada componente.
Exemplo clássico:
import { useEffect, useState } from "react";
export default function Users() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true; // Necessário para evitar memory leak em unmount
fetch("https://api.domain.com/users")
.then(res => res.json())
.then(json => {
if (isMounted) {
setData(json);
setLoading(false);
}
})
.catch(err => {
if (isMounted) {
setError(err);
setLoading(false);
}
});
return () => { isMounted = false; };
}, []);
if (loading) return <p>Carregando usuários...</p>;
if (error) return <p>Erro ao carregar dados.</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
Dívida técnica que aparece no crescimento
- boilerplate repetitivo (
data/loading/error) para cada endpoint; - ausência de cache compartilhado por chave de dados;
- risco de race condition e updates após unmount;
- chamadas duplicadas quando múltiplos componentes pedem o mesmo recurso;
- comportamento inconsistente entre telas em navegação rápida.
Quando o projeto escala, esse padrão fragmenta governança e aumenta custo de manutenção.
---
2) A mudança de camada: TanStack Query como sincronização de estado remoto
Com React Query, a mentalidade muda de “buscar dados” para “sincronizar dados”.
Implementação:
import { useQuery } from "@tanstack/react-query";
export default function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ["users"],
queryFn: async () => {
const res = await fetch("https://api.domain.com/users");
if (!res.ok) throw new Error("Falha na comunicação com a API");
return res.json();
},
staleTime: 1000 * 60, // dado considerado fresco por 1 minuto
retry: 2 // retentativa automática para falhas transitórias
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Erro: {error.message}</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
Aqui, o componente deixa de carregar responsabilidade de política de cache e consistência. Essa política sobe para uma camada de infraestrutura do front.
---
3) O que muda de verdade na engenharia
3.1 Cache e deduplicação por chave
Com queryKey: ["users"], se 10 componentes pedirem a mesma fonte, o Query deduplica e compartilha resultado.
3.2 Stale-While-Revalidate nativo
Usuário recebe dado de cache instantâneo enquanto atualização acontece em background. Isso reduz “flicker” de loading e melhora percepção de velocidade.
3.3 Revalidação orientada a foco de janela
Ao voltar para a aba, o Query pode revalidar automaticamente e reduzir risco de dashboard obsoleto.
3.4 Retry com política
Falha transitória de rede não vira erro terminal imediato. A camada de dados tenta recuperar sem quebrar fluxo de navegação.
---
4) Regra de decisão (sem dogma)
| Caso de uso | Ferramenta recomendada | Justificativa técnica |
|---|---|---|
| Consumo de APIs REST/GraphQL | React Query | Estado remoto com cache, deduplicação e revalidação |
| Sincronização de estado de servidor | React Query | Reduz complexidade de Context/Redux para dados externos |
| Listeners de browser (resize/scroll) | useEffect | Efeito colateral de runtime, não estado remoto |
| Timers/intervals | useEffect | Controle de ciclo de vida e limpeza (clearInterval) |
| Integração DOM/ref/lib externa | useEffect | Orquestração local do componente |
---
5) Impacto real em performance e governança
Importante: a API do backend não fica “mais rápida” por mágica. O ganho vem de:
- menos requisição redundante;
- menos churn de render por loading intermitente;
- menor complexidade cognitiva da equipe no front;
- política de dados centralizada e previsível.
Separação de responsabilidade fica clara:
useEffect: ciclo de vida e integração local;- React Query: consistência, cache e sincronização de estado remoto.
---
6) Conclusão estratégica
Em aplicação orientada a dados (dashboard, SaaS operacional, plataforma com múltiplas visões), usar apenas useEffect para fetch vira gargalo arquitetural.
useEffect continua essencial, mas como ferramenta tática. Para governança de estado remoto e escala real, React Query é decisão estratégica de engenharia.
Quem trata dados remotos como primeira classe entrega frontend mais previsível, resiliente e com UX fluida sob carga.
Este post está licenciado sob CC BY-NC.
Comentários
Participe da discussão abaixo.
Comentários ainda não configurados. Adicione as opções do Cusdis em /assets/json/config/blog-comments-config.json.