Skip to content
Back to skills

Tanstack Query Expert

ASecurity

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).

  • 11 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 8, 2026
developmenttypescriptgoreactnextjsnodegitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add artubss/SKILLS-CLAUDE-CODE --skill tanstack-query-expert --agent claude-code

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.

Security grade badge for Tanstack Query Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/artubss-tanstack-query-expert/badge)](https://www.skillsdirectory.com/skills/artubss-tanstack-query-expert)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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!

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…