TanStack Query: o estado assíncrono que mudou como apps React buscam dados
1. O Problema Clássico de Gerenciamento de Estado Assíncrono no React
1.1 A lacuna entre estado síncrono (Redux, Context) e dados remotos
Durante anos, desenvolvedores React trataram dados de API como se fossem estado local. Ferramentas como Redux e Context API foram projetadas para estado síncrono — temas, formulários, preferências do usuário. Quando aplicadas a dados remotos, criavam uma camada de complexidade desnecessária: actions, reducers, middlewares para chamadas assíncronas, tudo para gerenciar algo que o navegador já faz bem (cache HTTP).
1.2 Boilerplate e complexidade: loading, error, refetch, cache manual
Cada requisição exigia gerenciar manualmente quatro estados:
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setIsLoading(true);
fetch('/api/users')
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setIsLoading(false));
}, []);
Esse padrão se repetia dezenas de vezes por aplicação. Sem cache, sem refetch automático, sem deduplicação de requisições.
1.3 Por que useEffect + useState não escala para chamadas de API
Além do boilerplate, useEffect introduz race conditions, memory leaks (se o componente desmontar antes da resposta) e não oferece sincronização entre componentes que consomem o mesmo dado. Cada componente faz sua própria requisição, duplicando tráfego e degradando performance.
2. TanStack Query: Filosofia e Conceitos Fundamentais
2.1 Estado do servidor vs. estado do cliente — a separação essencial
TanStack Query introduz uma distinção crucial: dados no servidor (usuários, posts, configurações) não pertencem ao React. São um cache local de uma fonte remota. O React não deve "possuir" esses dados, apenas espelhá-los.
2.2 Query Keys e Query Functions: identificação e busca de dados
Cada consulta é identificada por uma chave única (array) e uma função que retorna uma Promise:
const { data, isLoading } = useQuery({
queryKey: ['users', { page: 1 }],
queryFn: () => fetch('/api/users?page=1').then(res => res.json())
});
2.3 Cache inteligente, stale-while-revalidate e background refetch
TanStack Query implementa o padrão stale-while-revalidate: dados em cache são exibidos imediatamente, enquanto uma revalidação em background atualiza o cache. Se o usuário navegar para uma tela e voltar, os dados aparecem instantaneamente do cache, sem loader.
3. Configuração e Uso Básico em Projetos React
3.1 Instalação e setup do QueryClientProvider
npm install @tanstack/react-query
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserList />
</QueryClientProvider>
);
}
3.2 Exemplo prático: useQuery para buscar uma lista de usuários
function UserList() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(res => res.json()),
});
if (isLoading) return <p>Carregando...</p>;
if (isError) return <p>Erro: {error.message}</p>;
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
3.3 Tratamento de estados: isLoading, isError, data e error
TanStack Query expõe estados granulares: isLoading (primeira carga), isFetching (qualquer requisição, incluindo revalidação), isError, isSuccess. Isso permite UIs precisas sem lógica condicional complexa.
4. Mutations: Criando, Atualizando e Deletando Dados com TanStack Query
4.1 useMutation e o ciclo de vida de operações de escrita
const mutation = useMutation({
mutationFn: (newUser) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser),
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
4.2 Invalidando queries e atualizando cache após mutações
Após criar um usuário, invalidar a query ['users'] força uma revalidação automática. Alternativamente, é possível atualizar o cache diretamente com setQueryData, evitando requisições desnecessárias.
4.3 Otimistic updates: melhorando a UX com respostas instantâneas
const mutation = useMutation({
mutationFn: updateUser,
onMutate: async (updatedUser) => {
await queryClient.cancelQueries({ queryKey: ['users'] });
const previous = queryClient.getQueryData(['users']);
queryClient.setQueryData(['users'], old =>
old.map(u => u.id === updatedUser.id ? updatedUser : u)
);
return { previous };
},
onError: (err, newUser, context) => {
queryClient.setQueryData(['users'], context.previous);
},
});
5. Estratégias Avançadas de Cache e Performance
5.1 Configuração de staleTime e cacheTime para diferentes cenários
staleTime: tempo até o dado ser considerado obsoleto (default: 0, sempre refetch ao montar)cacheTime: tempo até o dado ser removido do cache após não ser usado (default: 5 minutos)
Para dados raramente alterados (lista de países), staleTime: 10 * 60 * 1000 evita requisições desnecessárias.
5.2 Paginação e infinite queries com useInfiniteQuery
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['users'],
queryFn: ({ pageParam = 1 }) => fetch(`/api/users?page=${pageParam}`),
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
});
5.3 Prefetching e query hydration para SSR/SSG (Next.js)
// Em getServerSideProps
const queryClient = new QueryClient();
await queryClient.prefetchQuery(['users'], fetchUsers);
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
6. Padrões de Arquitetura e Organização de Queries
6.1 Custom hooks encapsulando queries e mutations por domínio
function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
}
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createUser,
onSuccess: () => queryClient.invalidateQueries(['users']),
});
}
6.2 Separando lógica assíncrona da UI com hooks tipados
Cada domínio (users, posts, comments) tem seu próprio arquivo de hooks. A UI chama hooks, nunca lida diretamente com fetch.
6.3 Testabilidade: mockando queries e mutations em testes unitários
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
function TestWrapper({ children }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
7. Casos de Uso Reais e Comparação com Alternativas
7.1 Exemplo completo: dashboard com múltiplas queries dependentes
function Dashboard() {
const { data: user } = useQuery(['user', userId], () => fetchUser(userId));
const { data: orders } = useQuery({
queryKey: ['orders', user?.id],
enabled: !!user?.id,
});
// orders só busca quando user estiver disponível
}
7.2 TanStack Query vs. RTK Query vs. SWR — quando escolher cada um
- TanStack Query: mais flexível, suporte a mutations complexas, optimistic updates, infinite queries
- RTK Query: integração nativa com Redux, ideal para apps que já usam Redux
- SWR: mais simples, foco em revalidação automática, menor curva de aprendizado
7.3 Limitações e armadilhas comuns
- Chaves mal definidas: usar objetos não serializáveis como chave causa loops infinitos
- Excesso de queries: cada filtro ou ordenação deve ser um parâmetro na queryKey, não uma nova query
- Cache desatualizado: esquecer de invalidar queries após mutations leva a dados obsoletos
8. Conclusão: Impacto no Ecossistema React e Tendências Futuras
8.1 Como TanStack Query mudou a mentalidade de gerenciamento de estado
TanStack Query popularizou a ideia de que estado do servidor merece tratamento especial. Reduziu drasticamente boilerplate, eliminou race conditions e tornou o cache algo trivial.
8.2 Integração com React Suspense e Server Components
Com React 18+, TanStack Query suporta Suspense, permitindo que componentes "suspendam" enquanto dados carregam. Em Server Components, o prefetching se torna ainda mais natural.
8.3 O futuro: estado assíncrono nativo no React e o papel do TanStack Query
React está explorando hooks nativos para dados assíncronos (use()). TanStack Query, no entanto, continuará relevante por oferecer cache, mutations, optimistic updates e infinite queries — funcionalidades que vão além do escopo do React core.
Referências
- Documentação Oficial do TanStack Query — Guia completo, API reference e exemplos práticos de todas as funcionalidades
- TanStack Query vs SWR vs RTK Query — Comparativo Técnico — Análise detalhada das diferenças entre as principais bibliotecas de data fetching
- React Query: The Guide to Server State in React — Série de artigos aprofundados por TkDodo, mantenedor do TanStack Query
- Optimistic Updates com TanStack Query — Documentação oficial sobre atualizações otimistas com exemplos práticos
- TanStack Query e Next.js — SSR e SSG — Guia oficial para integração com Next.js, incluindo prefetching e hydration
- React Query: A Complete Guide for Beginners — Tutorial passo a passo no freeCodeCamp cobrindo do básico ao avançado