O problema do useEffect + fetch na mão
Carregar dados do servidor parece simples: um useEffect que faz fetch e
guarda o resultado no estado. É a primeira coisa que aprendemos, e para um caso
isolado funciona:
function Perfil({ userId }) {
const [usuario, setUsuario] = useState(null);
const [carregando, setCarregando] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelado = false;
setCarregando(true);
fetch(`/api/usuarios/${userId}`)
.then((r) => {
if (!r.ok) throw new Error("Error " + r.status);
return r.json();
})
.then((data) => { if (!cancelado) setUsuario(data); })
.catch((e) => { if (!cancelado) setError(e); })
.finally(() => { if (!cancelado) setCarregando(false); });
return () => { cancelado = true; };
}, [userId]);
// ...renderização conforme carregando / error / usuario
}
Veja quanto código de encanamento existe para uma única requisição, e ainda faltam coisas. Quando o app cresce, este padrão fica aquém porque cada tela precisa reinventar:
- Cache: se dois componentes pedem
/api/usuarios/7, são feitas duas requisições. Não há memória compartilhada do que já foi baixado. - Deduplicação: várias chamadas simultâneas à mesma URL deveriam se fundir em uma única requisição em voo.
- Revalidação: os dados expiram. Quando são pedidos de novo? Ao refocar a janela, ao reconectar a rede, de tempos em tempos?
- Estados:
isLoading,error,isFetching(refetch em segundo plano), dados antigos enquanto chegam os novos... gerenciá-los na mão é frágil. - Cancelamento e condições de corrida: se
userIdmuda rápido, uma resposta lenta pode sobrescrever uma rápida (daí a flagcancelado).
Esses problemas não são do estado do cliente (o que o usuário digita ou seleciona): são do estado do servidor, dados que vivem remotamente, que cacheamos no cliente e que podem ficar desatualizados. As bibliotecas de data fetching nascem para gerenciar exatamente isso.
TanStack Query (React Query)
TanStack Query trata o estado do servidor como um recurso com cache. A
peça central é o hook useQuery:
import { useQuery } from "@tanstack/react-query";
function Perfil({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ["usuario", userId],
queryFn: () =>
fetch(`/api/usuarios/${userId}`).then((r) => r.json()),
staleTime: 60_000, // dados "frescos" durante 60 s
});
if (isLoading) return <p>Carregando…</p>;
if (error) return <p>Algo deu errado</p>;
return <h1>{data.nome}</h1>;
}
As chaves:
queryKey(["usuario", userId]): identifica a consulta no cache. Dois componentes com a mesma key compartilham os mesmos dados e é feita uma única requisição (deduplicação). SeuserIdmuda, a key muda e é pedido de novo.queryFn: a função que retorna uma promise com os dados. Você só se preocupa com como pedir, não com quando nem com cachear.data/isLoading/error: os estados já resolvidos para você. SemuseStatenemuseEffectmanuais.staleTime: quanto tempo os dados são considerados frescos. Enquanto estão, não são pedidos de novo; passado esse tempo ficam stale e são revalidados (por exemplo ao refocar a janela), mostrando enquanto isso os dados antigos.refetch: a própria query expõe uma funçãorefetch()para pedir de novo sob demanda (um botão "Atualizar").
Tudo o que antes você escrevia na mão —cache, deduplicação, revalidação, cancelamento— vem de fábrica.
SWR
SWR (da Vercel) segue a mesma filosofia com uma API mais minimalista. Seu nome vem de stale-while-revalidate: mostra os dados cacheados (stale) na hora e, em paralelo, revalida em segundo plano.
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((r) => r.json());
function Perfil({ userId }) {
const { data, error, isLoading } = useSWR(
`/api/usuarios/${userId}`,
fetcher
);
if (isLoading) return <p>Carregando…</p>;
if (error) return <p>Algo deu errado</p>;
return <h1>{data.nome}</h1>;
}
Aqui a chave de cache é a própria URL (primeiro argumento) e o fetcher é
a função de download. O SWR também deduplica requisições, revalida ao refocar
e reconectar, e expõe mutate() para atualizar o cache.
Ideia-chave: separe o estado do servidor (dados remotos cacheados → TanStack Query / SWR) do estado do cliente (UI local →
useState, Zustand...). Não coloque dados do servidor em um store global "na mão"; deixe que uma biblioteca de queries gerencie seu cache e seu frescor.
(Essas bibliotecas são instaladas à parte; aqui as estudamos de forma conceitual. Nos exercícios validáveis seguimos usando os hooks nativos do React.)