Especialista em TanStack Query (React Query) — gerenciamento de estado assíncrono. Aborda fetching de dados, configuração de stale time, mutations, atualizações otimistas e integração com Next.js App Router (SSR).
Installs into .claude/skills of the current project.
Are you the author of Tanstack Query Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/artubss-tanstack-query-expert)
---
name: tanstack-query-expert
description: "Especialista em TanStack Query (React Query) — gerenciamento de estado assíncrono. Aborda fetching de dados, configuração de stale time, mutations, atualizações otimistas e integração com Next.js App Router (SSR)."
risk: safe
source: community
date_added: "2026-03-07"
---
# Especialista em TanStack Query
Você é um especialista em TanStack Query (antigo React Query) em nível de produção. Você ajuda desenvolvedores a construir camadas robustas e performáticas de gerenciamento de estado assíncrono em aplicações React e Next.js. Você domina fetching de dados declarativo, invalidação de cache, atualizações otimistas de UI, sincronização em background, error boundaries e padrões de hidratação server-side rendering (SSR).
## Quando Usar Esta Habilidade
- Use ao configurar ou refatorar lógica de fetching de dados (substituindo `useEffect` + `useState`)
- Use ao projetar query keys (chaves baseadas em arrays, strictly typed)
- Use ao configurar comportamento global ou específico de `staleTime`, `gcTime` e `retry`
- Use ao escrever hooks `useMutation` para requisições POST/PUT/DELETE
- Use ao invalidar o cache (`queryClient.invalidateQueries`) após uma mutation
- Use ao implementar Atualizações Otimistas para feedback UX instantâneo
- Use ao integrar TanStack Query com Next.js App Router (Server Components + hidratação de Client Boundary)
## Conceitos Fundamentais
### Por que TanStack Query?
TanStack Query não é apenas para buscar dados; é um **gerenciador de estado assíncrono**. Ele lida com cache, atualizações em background, deduplicação de múltiplas requisições pelos mesmos dados, paginação e estados de loading/erro prontos para uso.
**Regra de Ouro:** Nunca use `useEffect` para buscar dados se TanStack Query está disponível no stack.
## Padrões de Definição de Queries
### O Padrão de Custom Hook (Melhor Prática)
Sempre abstraia chamadas de `useQuery` em custom hooks para encapsular a lógica de fetching, tipos TypeScript e query keys.
```typescript
import { useQuery } from '@tanstack/react-query';
// 1. Defina tipos rigorosos
type User = { id: string; name: string; status: 'active' | 'inactive' };
// 2. Defina a função fetcher
const fetchUser = async (userId: string): Promise<User> => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
};
// 3. Exporte um custom hook
export const useUser = (userId: string) => {
return useQuery({
queryKey: ['users', userId], // Query key baseada em array
queryFn: () => fetchUser(userId),
staleTime: 1000 * 60 * 5, // Dados frescos por 5 minutos (sem refetching em background)
enabled: !!userId, // Query dependente: só executa se userId existe
});
};
```
### Query Keys Avançadas
Query keys identificam exclusivamente o cache. Devem ser arrays, e a ordem importa.
```typescript
// Filtering / Sorting
useQuery({
queryKey: ['issues', { status: 'open', sort: 'desc' }],
queryFn: () => fetchIssues({ status: 'open', sort: 'desc' })
});
// Factory pattern para query keys (Altamente recomendado para aplicações grandes)
export const issueKeys = {
all: ['issues'] as const,
lists: () => [...issueKeys.all, 'list'] as const,
list: (filters: string) => [...issueKeys.lists(), { filters }] as const,
details: () => [...issueKeys.all, 'detail'] as const,
detail: (id: number) => [...issueKeys.details(), id] as const,
};
```
## Mutations & Invalidação de Cache
### Mutation Básica com Invalidação
Quando você modifica dados no servidor, deve avisar ao client cache que os dados antigos estão obsoletos.
```typescript
import { useMutation, useQueryClient } from '@tanstack/react-query';
export const useCreatePost = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newPost: { title: string }) => {
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
});
return res.json();
},
// Na sucesso, invalide o cache 'posts' para dispararum refetch em background
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
};
```
### Atualizações Otimistas
Dê ao usuário feedback instantâneo atualizando o cache *antes* do servidor responder, e reverta se a requisição falhar.
```typescript
export const useUpdateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateTodoFn,
// 1. Disparado imediatamente quando mutate() é chamado
onMutate: async (newTodo) => {
// Cancele refetches pendentes para que não sobrescrevam nossa atualização otimista
await queryClient.cancelQueries({ queryKey: ['todos'] });
// Faça snapshot do valor anterior
const previousTodos = queryClient.getQueryData(['todos']);
// Atualize otimisticamente para o novo valor
queryClient.setQueryData(['todos'], (old: any) =>
old.map((todo: any) => todo.id === newTodo.id ? { ...todo, ...newTodo } : todo)
);
// Retorne um objeto context com o valor salvo em snapshot
return { previousTodos };
},
// 2. Se a mutation falhar, use o context retornado de onMutate para reverter
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context?.previousTodos);
},
// 3. Sempre refetch após erro ou sucesso para garantir sincronização com servidor
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
};
```
## Integração com Next.js App Router
### Inicializando o Provider
```typescript
// app/providers.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minuto
refetchOnWindowFocus: false, // Previne refetching agressivo ao trocar de aba
},
},
})
)
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
```
### Server Component Pre-fetching (Hidratação)
Faça pre-fetch de dados no servidor e passe-os ao cliente sem prop-drilling ou `initialData`.
```typescript
// app/posts/page.tsx (Server Component)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
import PostsList from './PostsList'; // Client Component
export default async function PostsPage() {
const queryClient = new QueryClient();
// Faça pre-fetch dos dados no servidor
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPostsServerSide,
});
// Desidrate o cache e passe-o para a HydrationBoundary
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<PostsList />
</HydrationBoundary>
);
}
```
```typescript
// app/posts/PostsList.tsx (Client Component)
'use client'
import { useQuery } from '@tanstack/react-query';
export default function PostsList() {
// Isso NÃO disparará uma requisição de rede ao montar!
// Lê instantaneamente do cache desidratado do servidor.
const { data } = useQuery({
queryKey: ['posts'],
queryFn: fetchPostsClientSide,
});
return <div>{data.map(post => <p key={post.id}>{post.title}</p>)}</div>;
}
```
## Melhores Práticas
- ✅ **Faça:** Crie factories de Query Key para que você não erre ao digitar `['users']` vs `['user']` em arquivos diferentes.
- ✅ **Faça:** Configure um `staleTime` global (ex: `1000 * 60`) se seus dados não mudam a cada segundo. O `staleTime` padrão é `0`, o que significa que TanStack Query dispara um refetch em background a cada remount do componente por padrão.
- ✅ **Faça:** Use `queryClient.setQueryData` com moderação. Geralmente é melhor apenas fazer `invalidateQueries` e deixar TanStack Query refetch os dados frescos organicamente.
- ✅ **Faça:** Abstraia todas as chamadas `useMutation` e `useQuery` em custom hooks. Views devem apenas dizer `const { mutate } = useCreatePost()`.
- ❌ **Não Faça:** Passe callbacks primitivos inline diretamente para `useQuery` sem memoização se você confia em closures. (Em vez disso, confie no array de dependências `queryKey`).
- ❌ **Não Faça:** Sincronize dados de query em estado React local (ex: `useEffect(() => setLocalState(data), [data])`). Use os dados de query diretamente. Se você precisa de estado derivado, derive-o durante o render.
## Troubleshooting
**Problema:** Loop infinito de fetching na aba de network.
**Solução:** Verifique seu `queryFn`. Se sua lógica de `fetch` não está estruturada corretamente, ou lança uma exceção não tratada antes de retornar, TanStack Query vai fazer retry automaticamente até 3 vezes (padrão). Se envolvido em um `useEffect` instável, faz loop infinito. Verifique `retry: false` para debug.
**Problema:** Confusão entre `staleTime` vs `gcTime` (antigo `cacheTime`).
**Solução:** `staleTime` governa quando um refetch em background é disparado. `gcTime` governa por quanto tempo os dados inativos ficam na memória após o componente desmontar. Se `gcTime` < `staleTime`, os dados serão deletados antes de ficarem obsoletos!