English Edition Available: Read the full English version of this article: TanStack Query vs. useEffect: The Definitive Guide to Data Fetching, State Governance, and React Performance.
1. O Problema Real do useEffect para Fetch e a Mudança de Camada para Estado Remoto#
O useEffect não foi concebido para busca de dados; sua função original é sincronizar o componente com efeitos colaterais externos (como subscrições ou manipulação manual do DOM). Ao usá-lo para requisições assíncronas, é comum deparar-se com problemas crônicos de arquitetura:
- Race Conditions Silenciosas: Se o usuário alterna rapidamente entre páginas ou altera um filtro antes que a requisição anterior termine, múltiplos callbacks de resolução serão disparados. O componente exibirá os dados da requisição que terminar por último na rede, e não da mais recente iniciada pelo usuário.
- Ausência de Deduplicação de Requisições: Se três componentes na mesma tela precisam dos dados de usuários, o
useEffectfará três requisições HTTP paralelas. - Vazamento de Recursos (Memory Leaks): Em React 17 e anteriores, tentar atualizar o estado de um componente desmontado gerava o warning:
Can't perform a React state update on an unmounted component. No React 18+, esse aviso foi removido do console, mas o vazamento de recursos continua: a requisição HTTP continua rodando no navegador e a CPU processa a resposta em segundo plano de forma desnecessária. - Ausência de Cache: A interface pisca e mostra um loading toda vez que o componente é remontado, prejudicando a experiência de uso.
Comparativo Head-to-Head: useEffect vs. TanStack Query#
Ao comparar o padrão manual com useEffect e a abstração declarativa do TanStack Query, a diferença prática em complexidade de código e governança de estado fica evidente:
| Recurso / Critério | useEffect + useState Manual | TanStack Query (React Query) |
|---|---|---|
| Volume de Boilerplate | Alto (requer 3 estados manuais: data, loading, error) | Mínimo (1 chamada hook com retorno estruturado) |
| Cache em Memória | Inexistente (fetch repetido a cada remontagem) | Nativo e automático com controle via staleTime e gcTime |
| Deduplicação de Requests | Não (requisições simultâneas duplicam chamadas de rede) | Sim (agrupa e compartilha a mesma promise por Query Key) |
| Race Conditions | Frequentes em filtros/inputs rápidos (requer AbortController) | Resolvidas por design (chaves dinâmicas cancelam/ignoram queries antigas) |
| Revalidação em Background | Manual via listeners de foco de janela ou timers | Automática ao refocar aba (refetchOnWindowFocus) ou reconectar |
| Gerenciamento de Mutações | Efeitos colaterais manuais e refetch manual imperativo | useMutation com invalidação declarativa (invalidateQueries) |
| Rollback e Atualização Otimista | Extremamente complexo de sincronizar e reverter | Suporte de primeira classe com snapshots de contexto no rollback |
| Paginação e Infinite Scroll | Exige arquitetura complexa de junção de arrays | Hooks dedicados (useInfiniteQuery) com cursores prontos |
| Tratamento de Falhas (Retries) | Requer loops de retry manuais com backoff | Retries exponenciais automáticos configuráveis |
| Vazamentos de Memória (Leaks) | Alto risco ao desmontar componentes durante fetch | Limpeza automática gerenciada pelo ciclo de vida da query |
A Mudança de Camada: Sincronização de Estado Remoto#
Com React Query, a mentalidade muda de "buscar dados" para "sincronizar dados". O componente deixa de carregar a responsabilidade de gerenciar políticas de cache e consistência. Essa política sobe para uma camada dedicada de infraestrutura.
Ciclo de vida do cache#
O TanStack Query gerencia o dado em quatro estados sequenciais:
- Fresh: dado recente, não será revalidado automaticamente.
- Stale: dado considerado desatualizado; revalida em background no próximo trigger (foco de janela, interval, remount).
- Inactive: nenhum componente está inscrito nessa query key; o dado permanece no cache até
gcTimeexpirar. - Garbage Collected: o dado foi removido da memória. O próximo acesso fará um fetch do zero.
Usequery: padrões essenciais de busca e sincronização#
Abaixo está uma implementação típica utilizando tipagem TypeScript, validação de payload em tempo de execução com Zod para evitar estados corrompidos, e parâmetros de cacheamento dinâmico:
import { useQuery } from "@tanstack/react-query";
import { z } from "zod";
// 1. Definição do Schema de validação
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
const UsersArraySchema = z.array(UserSchema);
type User = z.infer<typeof UserSchema>;
// 2. Fetcher tipado e validado
const fetchUsers = async (status?: string): Promise<User[]> => {
const url = status ? `/api/users?status=${status}` : "/api/users";
const res = await fetch(url);
if (!res.ok) throw new Error("Erro na comunicação com a API");
const data = await res.json();
return UsersArraySchema.parse(data); // Validação estrita
};
export default function Users({ status }: { status?: string }) {
const { data, isLoading, isFetching, error } = useQuery<User[], Error>({
queryKey: ["users", status],
queryFn: () => fetchUsers(status),
// Configurações de Ciclo de Vida do Cache
staleTime: 1000 * 60 * 5, // Dado é considerado fresco por 5 minutos
gcTime: 1000 * 60 * 30, // Mantém dados em cache inativo por 30 minutos
retry: 2, // Retenta até 2 vezes antes de falhar
// Busca condicional: Só executa se o status estiver definido
enabled: !!status,
// Nota: `null`, `undefined`, `0` e `""` também são falsy e desabilitam a query.
// Use `enabled: status !== undefined` para permitir strings vazias como busca válida.
// Polling ativo (revalidação em background a cada 30 segundos)
refetchInterval: 30000,
refetchIntervalInBackground: false, // Pausa polling ao perder foco da aba
refetchOnWindowFocus: true, // Revalida ao focar na janela do app
});
if (isLoading) return <p>Carregando dados (primeira busca)...</p>;
if (error) return <p>Erro: {error.message}</p>;
return (
<div>
{isFetching && <span>Atualizando dados em segundo plano...</span>}
<ul>
{data?.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
}
Dependências dinâmicas no queryKey#
O queryKey funciona como o array de dependências do useEffect: sempre que um valor dentro dele mudar, o TanStack Query dispara automaticamente um novo fetch. Isso é o que torna o exemplo ["users", status] tão poderoso:
// Sempre que `status` mudar (ex: "active" → "inactive"),
// a query reexecuta automaticamente com o novo parâmetro
const { data } = useQuery({
queryKey: ["users", status], // status entra como parte da chave
queryFn: () => fetchUsers(status),
});
// Analogia com useEffect:
useEffect(() => {
fetchUsers(status);
}, [status]); // mesmo comportamento, mas sem cache, deduplication ou retry
Sempre que precisar que um dado refetch ao alterar filtros, páginas ou parâmetros de busca, inclua esses valores no queryKey. O TanStack Query garante que cada combinação de chave mantenha seu próprio cache isolado.
UX fluida em paginações#
Para evitar transições bruscas de tela durante paginações ou buscas dinâmicas, o parâmetro placeholderData permite manter o estado anterior enquanto novos dados são computados:
import { keepPreviousData } from "@tanstack/react-query";
const { data } = useQuery({
queryKey: ["users", page],
queryFn: () => fetchUsersPage(page),
placeholderData: keepPreviousData, // Evita layout shifts e spinners de loading
});
2. A Solução: Migração Prática de Código (Antes vs. Depois)#
Antes: o boilerplate excessivo com useEffect#
Para resolver race conditions (usando um sinalizador de cancelamento) e controlar estados de loading/error manualmente:
import { useState, useEffect } from 'react';
function ListaUsuarios() {
const [usuarios, setUsuarios] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let active = true;
setLoading(true);
fetch('/api/usuarios')
.then((res) => {
if (!res.ok) throw new Error('Falha ao obter usuários');
return res.json();
})
.then((data) => {
if (active) {
setUsuarios(data);
setLoading(false);
}
})
.catch((err) => {
if (active) {
setError(err);
setLoading(false);
}
});
return () => {
active = false;
};
}, []);
if (loading) return <Skeleton />;
if (error) return <ErroComponente mensagem={error.message} />;
return <TabelaUsuarios dados={usuarios} />;
}
Depois: declarativo com tanstack query (v5)#
import { useQuery } from '@tanstack/react-query';
function ListaUsuarios() {
const { data: usuarios, isLoading, error } = useQuery({
queryKey: ['usuarios'],
queryFn: () => fetch('/api/usuarios').then((r) => {
if (!r.ok) throw new Error('Erro de rede');
return r.json();
}),
});
if (isLoading) return <Skeleton />;
if (error) {
const mensagem = error instanceof Error ? error.message : 'Erro desconhecido';
return <ErroComponente mensagem={mensagem} />;
}
return <TabelaUsuarios dados={usuarios ?? []} />;
}
3. Tipagem com TypeScript e Tratamento Seguro de Erros#
No TanStack Query v5, o tipo padrão do objeto error é unknown por segurança, já que qualquer exceção pode ser lançada na queryFn. Acessar .message diretamente causa erros de compilação.
Tipagem forte dos dados e do erro#
É possível tipar a query passando parâmetros genéricos na chamada ou deixando a função resolver implicitamente:
interface Usuario {
id: number;
nome: string;
email: string;
}
// O Hook infere o tipo retornado por queryFn como `Usuario[] | undefined`
const { data, isLoading, error } = useQuery<Usuario[], Error>({
queryKey: ['usuarios'],
queryFn: async () => {
const res = await fetch('/api/usuarios');
if (!res.ok) throw new Error('Falha na API');
return res.json();
},
staleTime: 1000 * 60 * 5, // Dados válidos em cache por 5 minutos
});
if (error) {
// TypeScript agora sabe que error é do tipo Error
return <p>Ocorreu um erro: {error.message}</p>;
}
Uso com react error boundaries#
Se você prefere capturar erros usando um Error Boundary global do React, utilize a propriedade throwOnError:
const { data } = useQuery({
queryKey: ['usuarios'],
queryFn: fetchUsuarios,
throwOnError: true, // Propaga o erro para o ancestral Error Boundary
});
4. Buscas Condicionais, Dependentes e Suporte a Suspense#
Muitas vezes, a busca de dados depende de uma variável (ex: um ID selecionado) ou deve acontecer apenas após uma ação do usuário. A flag enabled controla essa execução:
const { data: detalhes, isLoading } = useQuery({
queryKey: ['usuario', usuarioId],
queryFn: () => fetch(`/api/usuarios/${usuarioId}`).then(r => r.json()),
enabled: !!usuarioId, // Só dispara o fetch se usuarioId for válido
});
Data Fetching Declarativo com Suspense#
O TanStack Query suporta integração nativa com <Suspense> do React para simplificar estados de carregamento. Em vez de checar isLoading em cada componente, você delega o estado de loading para um Suspense boundary mais acima na árvore:
import { useSuspenseQuery } from "@tanstack/react-query";
import { Suspense } from "react";
// Hook: use useSuspenseQuery (não useQuery) para modo Suspense
function UserList({ status }: { status: string }) {
const { data } = useSuspenseQuery({
queryKey: ["users", status],
queryFn: () => fetchUsers(status),
});
// data aqui é sempre definido (nunca undefined)
return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// Pai: gerencia o estado de loading via boundary
export function UsersPage({ status }: { status: string }) {
return (
<Suspense fallback={<p>Carregando usuários...</p>}>
<UserList status={status} />
</Suspense>
);
}
5. Operações de Escrita: Mutações com useMutation e Invalidação de Cache#
Buscar dados (queries) é apenas metade do ciclo CRUD. Para operações de escrita (POST, PUT, DELETE), o TanStack Query fornece o hook useMutation, integrado com invalidação de cache para manter a interface atualizada.
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface NovoUsuario {
nome: string;
email: string;
}
function CriarUsuario() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (novoUsuario: NovoUsuario) => {
const res = await fetch('/api/usuarios', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(novoUsuario),
});
if (!res.ok) throw new Error('Erro ao criar usuário');
return res.json();
},
onSuccess: () => {
// Invalida o cache e força todos os componentes inscritos em 'usuarios' a rebuscar os dados
queryClient.invalidateQueries({ queryKey: ['usuarios'] });
},
});
return (
<div>
{mutation.isError && <p>Erro: {mutation.error.message}</p>}
<button
onClick={() => mutation.mutate({ nome: 'Percio', email: '[email protected]' })}
disabled={mutation.isPending}
>
{mutation.isPending ? 'Salvando...' : 'Adicionar Usuário'}
</button>
</div>
);
}
Controle Avançado de Mutações e Invalidação#
Para fluxos de alteração de dados (POST, PUT, DELETE), o React Query fornece o hook useMutation. Ao contrário de queries, mutações não disparam requisições de forma implícita e são essenciais para gerenciar side-effects, revalidações e rollbacks.
Invalidação simples de cache pós-sucesso#
import { useMutation, useQueryClient } from "@tanstack/react-query";
interface CreateUserInput {
name: string;
email: string;
}
export function CreateUserButton() {
const queryClient = useQueryClient();
const mutation = useMutation<User, Error, CreateUserInput>({
mutationFn: async (newUser) => {
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newUser),
});
if (!res.ok) throw new Error("Erro ao criar usuário");
return res.json();
},
onSuccess: () => {
// Invalida e força re-fetch imediato da lista de usuários para manter sync
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
return (
<button
onClick={() => mutation.mutate({ name: "João", email: "[email protected]" })}
disabled={mutation.isPending}
>
{mutation.isPending ? "Salvando..." : "Criar Usuário"}
</button>
);
}
Optimistic updates (atualização otimista) com rollback#
Em redes móveis ou de alta latência, é possível atualizar o cache local instantaneamente e fazer rollback caso a API falhe:
const mutation = useMutation({
mutationFn: updateUserData,
// Executa assim que a mutação é chamada
onMutate: async (newData) => {
// 1. Cancela refetches da lista para evitar sobrescrever o estado otimista
await queryClient.cancelQueries({ queryKey: ["users"] });
// 2. Salva o snapshot do cache atual (para rollback de emergência)
const previousUsers = queryClient.getQueryData<User[]>(["users"]);
// 3. Atualiza o cache de forma otimista
queryClient.setQueryData<User[]>(["users"], (old) =>
old ? old.map(u => u.id === newData.id ? { ...u, ...newData } : u) : []
);
// Retorna o snapshot salvo
return { previousUsers };
},
// Se a mutação falhar, reverte para o estado anterior
onError: (err, newData, context) => {
if (context?.previousUsers) {
queryClient.setQueryData(["users"], context.previousUsers);
}
},
// Roda sempre após sucesso ou falha, garantindo o sync final
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
6. Configuração Global do QueryClient e DevTools#
Configurar opções padrão de forma global evita que você precise digitar políticas de cache em todas as consultas.
Configurando o client com políticas de cache#
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // Dados frescos por 5 minutos
gcTime: 1000 * 60 * 30, // Mantém no cache (Garbage Collector) por 30 minutos
retry: 3, // Tenta 3 vezes antes de falhar
refetchOnWindowFocus: true, // Revalida automaticamente se focar na aba
refetchOnReconnect: true, // Revalida se a internet voltar
},
},
});
Habilitando o tanstack query devtools#
O DevTools oficial é essencial para depurar estados do cache, conferir chaves e depurar mutações em tempo de execução:
pnpm add @tanstack/react-query-devtools
Adicione o painel do DevTools logo abaixo do QueryClientProvider na raiz da sua aplicação:
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
ReactDOM.createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
7. Otimização de Performance, Governança de Cache (staleTime vs. gcTime) e UX#
A. Polling automático (real-time)#
Para painéis de monitoramento que necessitam de atualização periódica e contínua:
const { data } = useQuery({
queryKey: ['dashboard-status'],
queryFn: fetchStatus,
refetchInterval: 10000, // Executa a query a cada 10 segundos
refetchIntervalInBackground: false, // Desativa o polling quando a aba estiver em background
});
B. Paginação fluida com placeholderData#
Evite que a interface sofra layout shifts (piscadas com loading skeleton) ao trocar de página no navegador. O parâmetro placeholderData mantém os dados antigos visíveis enquanto carrega a nova página:
import { keepPreviousData } from '@tanstack/react-query';
function ListaPaginada({ pagina }: { pagina: number }) {
const { data, isPlaceholderData } = useQuery({
queryKey: ['usuarios', pagina],
queryFn: () => fetch(`/api/usuarios?page=${pagina}`).then(r => r.json()),
placeholderData: keepPreviousData, // UX perfeito: exibe página atual opaca enquanto busca a próxima
});
return (
<div style={{ opacity: isPlaceholderData ? 0.6 : 1, transition: 'opacity 0.2s' }}>
<TabelaUsuarios dados={data?.resultado ?? []} />
</div>
);
}
C. Transformação de dados com select#
Permite filtrar ou transformar a resposta da query antes de expô-la ao componente, otimizando renders (o componente só re-renderiza se os dados transformados mudarem):
const { data: totalAtivos } = useQuery({
queryKey: ['usuarios'],
queryFn: fetchUsuarios,
select: (usuarios: Usuario[]) => usuarios.filter(u => u.status === 'ativo').length,
});
8. Segurança e Validação de Dados em Tempo de Execução com Zod#
Se a API retornar um payload com propriedades ausentes ou tipos trocados, seu código TypeScript pode falhar em runtime. Validar o payload no fetcher com Zod garante a integridade dos dados na entrada do app:
import { z } from 'zod';
import { useQuery } from '@tanstack/react-query';
const UsuarioSchema = z.object({
id: z.number(),
nome: z.string(),
email: z.string().email(),
});
const ListaUsuariosSchema = z.array(UsuarioSchema);
function HookUsuariosValidados() {
return useQuery({
queryKey: ['usuarios-validados'],
queryFn: async () => {
const res = await fetch('/api/usuarios');
const data = await res.json();
// O parse lança um erro detalhado se a API quebrar o contrato
return ListaUsuariosSchema.parse(data);
},
});
}
9. Matriz de Decisão: Quando Ainda Usar useEffect e Alternativas no Ecossistema#
1. Centralização de chaves (query key factories)#
Evite o uso de strings soltas espalhadas pelo projeto para evitar erros de digitação ao invalidar o cache:
export const queryKeys = {
usuarios: {
all: ['usuarios'] as const,
list: (filtro: string) => [...queryKeys.usuarios.all, 'lista', filtro] as const,
detail: (id: number) => [...queryKeys.usuarios.all, 'detalhe', id] as const,
},
};
// Uso:
useQuery({ queryKey: queryKeys.usuarios.detail(1), queryFn: ... })
2. Comparações e alternativas#
Embora o TanStack Query seja o ecossistema mais adotado, é bom conhecer as alternativas para cada cenário de projeto:
| Biblioteca | Peso (Gzipped) | Casos de Uso Recomendados | Principais Diferenciais |
|---|---|---|---|
| TanStack Query | ~13kB | Aplicações SaaS complexas, dashboards robustos | Gestão de cache granular, mutações, excelente ecossistema e devtools. |
| SWR (Vercel) | ~4kB | Landing pages dinâmicas, projetos simples de Next.js | Extremamente leve, API minimalista, otimizado para revalidação rápida. |
| RTK Query | Incluso no Redux | Projetos corporativos grandes que já utilizam Redux Toolkit | Centralização do estado e caching integrados no mesmo store Redux. |
| Apollo Client | ~30kB | Projetos focados 100% em infraestrutura GraphQL | Cache normalizado de objetos GraphQL e geração de queries robustas. |
Fonte dos pesos das bibliotecas (Gzipped): Bundlephobia.
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 |
Alternativas no Ecossistema (SWR, RTK Query)#
Embora o TanStack Query seja o padrão de facto na comunidade React, vale notar outras ferramentas e abordagens equivalentes:
- SWR (Vercel): Extremamente leve e simples, ideal para aplicações menores. Possui foco na política Stale-While-Revalidate nativa.
- RTK Query (Redux Toolkit): Recomendado caso sua aplicação já use Redux como governança de estado local abrangente. Evita carregar duas bibliotecas de estado robustas simultaneamente.
- Apollo Client: Solução madura para ecossistemas 100% voltados a GraphQL com cache normativo integrado.
10. Referência Rápida de Estados, Checklist Operacional e Conclusão#
Referência Rápida de Estados de Consulta#
| Estado | Hook | Finalidade Operacional |
|---|---|---|
isLoading | useQuery | Primeira busca iniciada (nenhum dado em cache está disponível) |
isFetching | useQuery | Requisição ativa em andamento (independente de ter cache ou ser refetch) |
isPending | useMutation | Execução da mutação de escrita ativa no servidor |
isSuccess | Ambos | Ação concluída com dados persistidos ou retornados |
error | Ambos | Exceção capturada na promessa da chamada HTTP |
isLoading vs isFetching - a diferença prática#
isLoadingétrueapenas na primeira busca, quando não há absolutamente nenhum dado em cache para essa query key. Use para renderizar o skeleton ou spinner inicial da página.isFetchingétruesempre que há qualquer requisição em andamento - inclusive refetches em background, polling por interval, ou refetch por foco de janela. Use para exibir um indicador sutil (ex: spinner pequeno no canto) sem esconder os dados já exibidos.
// Padrão correto para UX progressiva:
if (isLoading) return <FullPageSkeleton />; // Primeira carga: sem dado algum
return (
<div>
{isFetching && <SmallSpinner />} {/* Revalidando em background */}
<UserList data={data} />
</div>
);
11. Perguntas Frequentes (FAQ): useEffect vs TanStack Query#
Quando ainda devo usar o useEffect em vez do React Query?#
Devo usar o useEffect para sincronizar componentes com efeitos colaterais locais do navegador que não envolvem estado remoto de servidores. Exemplos diretos incluem: escutar eventos globais de redimensionamento de janela ou rolagem (window.addEventListener('resize')), configurar intervalos e timers que precisam de limpeza (setInterval / clearInterval), manipular diretamente referências do DOM (ref.current.focus()), ou inicializar bibliotecas externas imperativas de terceiros (como players de vídeo ou gráficos canvas locais). Para qualquer dado vindo de APIs HTTP, REST ou GraphQL, o TanStack Query é a escolha recomendada.
O TanStack Query substitui bibliotecas de estado global como Zustand ou Redux?#
Na maior parte das aplicações web modernas, sim. Em meus projetos e diagnósticos de arquitetura, observo que cerca de 80% do estado que desenvolvedores colocam no Redux ou Zustand é puramente estado remoto de servidor (dados de usuários, listas de produtos, pedidos, configurações da API). Ao delegar o estado remoto e o cache para o TanStack Query, as bibliotecas de estado global ficam reservadas apenas para estado do cliente (como tema escuro/claro ativo, abas selecionadas, carrinhos locais não salvos ou modais abertos), simplificando drasticamente a base de código.
Como o TanStack Query previne race conditions durante buscas com filtros rápidos?#
O TanStack Query indexa cada busca através de uma queryKey única (por exemplo, ['users', filterText]). Quando o usuário digita rapidamente e altera o filtro, uma nova chave de consulta é criada imediatamente. Se a requisição anterior ainda estiver transitando na rede, o TanStack Query desconsidera o resultado da chave anterior ou cancela a requisição ativa via AbortSignal automático (caso você passe o sinal no queryFn), garantindo que apenas a resposta da busca mais recente seja entregue ao componente.
Qual é a diferença prática entre isLoading e isFetching na experiência do usuário?#
Em termos de experiência de usuário (UX), a distinção é fundamental: isLoading é verdadeiro apenas na primeira busca daquela chave, quando não há absolutamente nenhum dado em cache (momento ideal para exibir um skeleton ou placeholder de tela cheia). Já o isFetching é verdadeiro sempre que qualquer chamada de rede está em andamento para aquela query, incluindo revalidações em segundo plano ao refocar a aba. Utilizo o isFetching para mostrar um pequeno spinner discreto no canto da interface, sem nunca ocultar o conteúdo que o usuário já está visualizando.
O React Query adiciona peso excessivo ao bundle final da aplicação?#
Não. O pacote @tanstack/react-query é altamente modular e possui suporte completo a tree-shaking moderno. Quando empacotado para produção, ele adiciona aproximadamente 12 a 13 KB (gzipped) ao bundle JavaScript da aplicação. Esse custo mínimo de transferência é amplamente compensado pela eliminação imediata de dezenas de hooks customizados, dependências de gerenciamento de estado redundantes e linhas repetitivas de código manual de fetch e tratamento de erros.
Considerações Finais de Arquitetura#
O useEffect para fetch de dados funciona como um protótipo, mas falha ao escalar. Para aplicações React modernas e de nível empresarial, adotar o TanStack Query reduz o código de estado redundante, melhora a experiência de navegação do usuário através de cache e poling seguro, e elimina bugs crônicos de concorrência.
Artigos Relacionados e Versão em Inglês: - Leia a versão completa em inglês deste guia: TanStack Query vs. useEffect: The Definitive Guide to Data Fetching, State Governance, and React Performance. - Se você também trabalha com ecossistemas fullstack modernos, recomendo ler meu guia prático: Do Caos à Ordem: Jornada Resolvendo Logs no Next.js 16 Turbopack. - Para telemetria de microsserviços e governança de requisições em tempo real, conheça o NEXUS Platform.
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários