К основному содержанию
скилл CX Codex

tanstack-query · mindrally — скилл для Claude Code: практики TanStack Query v5

Скилл задаёт агенту правила работы с серверным состоянием в React: ключи запросов, queryOptions, мутации и переход с v4 на v5.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add Mindrally/skills --skill tanstack-query -a claude-code
Открыть на GitHub

Что делает tanstack-query

Скилл описывает, как писать код на TanStack Query v5. Агент получает набор правил: каждый запрос получает стабильный сериализуемый ключ, чтение и запись не смешиваются, клиентское состояние остаётся в useState или контексте, а не в кэше запросов. Отдельный блок посвящён различиям v4 и v5 — объектный синтаксис вместо позиционных аргументов, isPending вместо isLoading, gcTime вместо cacheTime, useSuspenseQuery вместо опции suspense: true.

Внутри есть рекомендуемая структура проекта: папки api/endpoints, queries, hooks/queries, hooks/mutations, providers. Показаны фабрика ключей вроде userKeys, хелпер queryOptions() для переиспользования определения запроса в компонентах, загрузчиках роутера и префетче, кастомные хуки, зависимые и параллельные запросы через useQueries. Настройка QueryClient вынесена в providers/QueryProvider.tsx.

Главное, на чём спотыкаются: QueryClient создаётся один раз в корне приложения. Если инстанцировать его внутри компонента, кэш сбрасывается на каждом рендере. Скилл написан под TypeScript, поэтому типизация ответов идёт через дженерики useQuery и UseQueryOptions.

Какие задачи решает

  • переписать запросы с позиционных аргументов v4 на объектный синтаксис v5
  • собрать фабрику ключей, чтобы инвалидация кэша не превращалась в угадайку
  • вынести определение запроса в queryOptions() и переиспользовать его в загрузчике роутера
  • сделать оптимистичное обновление в мутации с откатом при ошибке
  • настроить QueryClient с staleTime и gcTime в одном месте

Примеры запросов агенту

  • Напиши хук useUser с ключом из фабрики userKeys и типизированным ответом
  • Переведи этот useQuery на синтаксис v5 и замени cacheTime на gcTime
  • Сделай оптимистичное обновление в useCreateUser с откатом кэша при ошибке

Как установить tanstack-query

1Через Skills CLI (Vercel)

npx skills add Mindrally/skills --skill tanstack-query -a claude-code

2Вручную: положить файлы

~/.claude/skills/tanstack-query/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/tanstack-query/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о tanstack-query

Как установить tanstack-query в Claude?

Выполните npx skills add Mindrally/skills --skill tanstack-query -a claude-code. Скилл появится в ~/.claude/skills/tanstack-query/SKILL.md.

Что делает скилл tanstack-query?

Он даёт агенту правила TanStack Query v5: фабрики ключей, хелпер queryOptions(), кастомные хуки, мутации, оптимистичные обновления, бесконечные запросы и режим Suspense.

Чем v5 отличается от v4 в этом скилле?

Разбор breaking changes: только объектный синтаксис, isPending вместо isLoading в мутациях, gcTime вместо cacheTime, useSuspenseQuery вместо suspense: true и placeholderData: keepPreviousData для пагинации.

Исходный текст

SKILL.md · Apache-2.0
---
name: tanstack-query
description: "Best practices for TanStack Query v5 (React Query) server state management, including query key factories, the queryOptions helper, mutations, optimistic updates, infinite queries, and Suspense mode. Use when fetching or caching server data in React, writing custom query/mutation hooks, setting up a QueryClient, implementing optimistic updates, or migrating v4 patterns to v5."
metadata:
  maintainer: Mindrally
  source: https://github.com/Mindrally/skills
---

# TanStack Query Best Practices

TanStack Query (formerly React Query) handles server-state caching, background updates, and stale-data management out of the box. This skill covers v5 patterns and APIs — v5 introduced several breaking changes from v4 that older examples online still don't reflect.

## Core Principles

- Use TanStack Query for all server state management and data fetching; it is not a general client-state manager — keep client-only state in `useState`/context/a state library instead
- Minimize `useEffect` and `useState` for server data; favor TanStack Query's built-in state management
- Every query needs a stable, serializable query key that uniquely describes the data it holds
- Mutations handle writes; queries handle reads — don't blur this boundary
- Implement proper error handling with user-friendly messages
- Use TypeScript for full type safety with query responses

## v5 Breaking Changes to Watch For

If you see or write any of these v4 patterns, update them:

- **Object syntax only**: `useQuery`, `useInfiniteQuery`, etc. no longer accept positional arguments (`useQuery(key, fn, options)`). Always pass a single options object: `useQuery({ queryKey, queryFn, ...options })`.
- **`isPending` replaces `isLoading`** as the name for "no data yet and a fetch is in flight" on `useMutation`. On `useQuery`, `isPending` means no cached data exists at all; `isLoading` is now derived (`isPending && isFetching`) and still usable for the classic "first load" spinner case.
- **`cacheTime` renamed to `gcTime`** (garbage collection time).
- **`queryOptions()` helper** for defining reusable, typed query definitions shared between components, loaders, and prefetch calls.
- **`useSuspenseQuery`** (and `useSuspenseInfiniteQuery`) for Suspense-based data fetching, replacing the old `suspense: true` option.
- **`placeholderData: keepPreviousData`** replaces the old `keepPreviousData: true` boolean for pagination.

## Project Structure

```
src/
  api/
    client.ts             # API client configuration
    endpoints/
      users.ts            # User-related API calls
      posts.ts            # Post-related API calls
  queries/
    postKeys.ts            # Query key factory
    postQueryOptions.ts     # queryOptions() definitions
  hooks/
    queries/
      useUsers.ts         # User query hooks
      usePosts.ts         # Post query hooks
    mutations/
      useCreateUser.ts    # User mutation hooks
  providers/
    QueryProvider.tsx     # Query client provider setup
  types/
    api.ts                # API response types
```

## Setup and Configuration

### Query Client Configuration

```typescript
// providers/QueryProvider.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes; defaults to 0 (always stale) if unset
      gcTime: 1000 * 60 * 30,   // 30 minutes (formerly cacheTime)
      retry: (failureCount, error: any) => error?.status !== 404 && failureCount < 3,
      refetchOnWindowFocus: true,
      refetchOnReconnect: true,
    },
    mutations: {
      retry: 1,
    },
  },
});

export function QueryProvider({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}
```

Instantiate `QueryClient` once at the app root — never inside a component, or the cache resets on every render.

## Query Best Practices

### 1. Query Key Organization

Use consistent, hierarchical query keys for efficient cache management:

```typescript
// Query key factory pattern
export const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  list: (filters: UserFilters) => [...userKeys.lists(), filters] as const,
  details: () => [...userKeys.all, 'detail'] as const,
  detail: (id: string) => [...userKeys.details(), id] as const,
};
```

### 2. queryOptions Helper (v5)

Define a query once with `queryOptions()` and reuse the same definition across components, router loaders, and prefetch calls — this keeps the query key, query function, and options in one place instead of duplicating them:

```typescript
// queries/postQueryOptions.ts
import { queryOptions } from '@tanstack/react-query';
import { postKeys } from './postKeys';
import { fetchPost } from '@/api/endpoints/posts';

export const postQueryOptions = (id: string) =>
  queryOptions({
    queryKey: postKeys.detail(id),
    queryFn: () => fetchPost(id),
    staleTime: 1000 * 60 * 5,
  });

// In a component
const { data } = useQuery(postQueryOptions(postId));

// In a TanStack Router loader — eliminates loading spinners on navigation
export const Route = createFileRoute('/posts/$postId')({
  loader: ({ params, context: { queryClient } }) =>
    queryClient.ensureQueryData(postQueryOptions(params.postId)),
});
```

Always define `queryOptions` outside components — never inline a fresh object literal in every `useQuery()` call — so the definition can be shared and prefetched.

### 3. Custom Query Hooks

Create reusable, typed query hooks when a `queryOptions()` factory isn't reused elsewhere:

```typescript
// hooks/queries/useUser.ts
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { userKeys } from '@/api/queryKeys';
import { getUser, User } from '@/api/endpoints/users';

export function useUser(
  userId: string,
  options?: Omit<UseQueryOptions<User, Error>, 'queryKey' | 'queryFn'>
) {
  return useQuery({
    queryKey: userKeys.detail(userId),
    queryFn: () => getUser(userId),
    enabled: !!userId,
    ...options,
  });
}
```

### 4. Dependent Queries

Handle queries that depend on other data:

```typescript
function useUserPosts(userId: string) {
  const { data: user } = useUser(userId);

  return useQuery({
    queryKey: ['posts', { userId }],
    queryFn: () => fetchUserPosts(userId),
    enabled: !!user, // Only run when user data is available
  });
}
```

### 5. Parallel Queries

Fetch multiple resources simultaneously:

```typescript
import { useQueries } from '@tanstack/react-query';

function useMultipleUsers(userIds: string[]) {
  return useQueries({
    queries: userIds.map((id) => ({
      queryKey: userKeys.detail(id),
      queryFn: () => getUser(id),
    })),
  });
}
```

## Mutation Best Practices

### 1. Basic Mutations

```typescript
const { mutate, mutateAsync, isPending } = useMutation({
  mutationFn: (input: CreatePostInput) => createPost(input),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: postKeys.lists() });
    toast.success('Post created!');
  },
  onError: (error) => {
    toast.error(error.message);
  },
});

// Usage
mutate({ title: 'Hello', body: '...' });
```

`isPending` is the v5 name for "mutation in flight" (v4 called this `isLoading` on mutations too — that name is gone).

### 2. Optimistic Updates

Provide instant feedback while mutations are in flight:

```typescript
import { useMutation, useQueryClient } from '@tanstack/react-query';

function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateUser,
    onMutate: async (newUser) => {
      // Cancel outgoing refetches
      await queryClient.cancelQueries({ queryKey: userKeys.detail(newUser.id) });

      // Snapshot previous value
      const previousUser = queryClient.getQueryData(userKeys.detail(newUser.id));

      // Optimistically update
      queryClient.setQueryData(userKeys.detail(newUser.id), newUser);

      return { previousUser };
    },
    onError: (err, newUser, context) => {
      // Rollback on error
      queryClient.setQueryData(
        userKeys.detail(newUser.id),
        context?.previousUser
      );
    },
    onSettled: (data, error, variables) => {
      // Refetch after error or success
      queryClient.invalidateQueries({ queryKey: userKeys.detail(variables.id) });
    },
  });
}
```

### 3. Cache Invalidation

Properly invalidate related queries after mutations:

```typescript
function useDeleteUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: deleteUser,
    onSuccess: () => {
      // Invalidate all user-related queries
      queryClient.invalidateQueries({ queryKey: userKeys.all });
    },
  });
}
```

Other cache operations worth knowing:

```typescript
// Remove from cache entirely (not just marked stale)
queryClient.removeQueries({ queryKey: userKeys.detail(id) });

// Directly write to the cache without a refetch
queryClient.setQueryData(userKeys.detail(id), newData);
```

## Error Handling

### 1. Global Error Handler

```typescript
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      throwOnError: false,
    },
    mutations: {
      onError: (error) => {
        // Global error handling (e.g., toast notification)
        toast.error(error.message);
      },
    },
  },
});
```

### 2. Component-Level Error Handling

```typescript
function UserProfile({ userId }: { userId: string }) {
  const { data, error, isPending, isError } = useUser(userId);

  if (isPending) return <Skeleton />;
  if (isError) return <ErrorMessage error={error} />;

  return <UserCard user={data} />;
}
```

### 3. Conditional Retry Logic

Skip retries for errors that will never succeed on retry, like 404s:

```typescript
retry: (failureCount, error) => {
  if (error.status === 404) return false;
  return failureCount < 3;
},
```

### 4. Suspense Mode (v5)

Use `useSuspenseQuery` for Suspense-based data fetching instead of the old `suspense: true` option — it also narrows the return type since `data` can never be `undefined`:

```typescript
import { useSuspenseQuery } from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';
import { Suspense } from 'react';

function UserProfile({ userId }: { userId: string }) {
  // No need to check isPending — Suspense handles the loading state
  const { data } = useSuspenseQuery(userQueryOptions(userId));
  return <UserCard user={data} />;
}

function App() {
  return (
    <ErrorBoundary fallback={<ErrorFallback />}>
      <Suspense fallback={<Loading />}>
        <UserProfile userId="123" />
      </Suspense>
    </ErrorBoundary>
  );
}
```

Use `throwOnError: true` on a regular `useQuery` if you want errors to bubble to the nearest `ErrorBoundary` without switching to Suspense.

## Performance Optimization

### 1. Select and Transform Data

Only subscribe to the data you need:

```typescript
function useUserName(userId: string) {
  return useUser(userId, {
    select: (user) => user.name,
  });
}
```

### 2. Prefetching

Prefetch data before it's needed — on hover, or during routing:

```typescript
function UserList() {
  const queryClient = useQueryClient();

  const prefetchUser = (userId: string) => {
    queryClient.prefetchQuery(userQueryOptions(userId));
  };

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id} onMouseEnter={() => prefetchUser(user.id)}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}
```

### 3. Infinite Queries

Handle paginated data efficiently:

```typescript
import { useInfiniteQuery } from '@tanstack/react-query';

function useInfinitePosts() {
  return useInfiniteQuery({
    queryKey: postKeys.lists(),
    queryFn: ({ pageParam }) => fetchPosts(pageParam),
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
    getPreviousPageParam: (firstPage) => firstPage.prevCursor,
  });
}

// data.pages is an array of page results — flatten for rendering
const allPosts = data?.pages.flatMap((page) => page.items) ?? [];
```

Use `placeholderData: keepPreviousData` (imported from `@tanstack/react-query`) on paginated or filtered queries to keep showing the previous page's data while the next page loads, instead of flashing a loading state:

```typescript
import { keepPreviousData, useQuery } from '@tanstack/react-query';

useQuery({
  queryKey: postKeys.list({ page }),
  queryFn: () => fetchPosts({ page }),
  placeholderData: keepPreviousData,
});
```

Use `notifyOnChangeProps` to limit re-renders to only the specific result properties a component actually reads.

## TypeScript Tips

- Always type `queryFn` return value explicitly, or infer it from typed API functions
- Use `QueryObserverResult<TData, TError>` to type hook return values
- Use `UseMutationResult<TData, TError, TVariables>` for mutations

## Key Conventions

1. **Feature-based organization**: Group query hooks and `queryOptions` factories within feature-specific directories
2. **Consistent query keys**: Use factory functions for type-safe, organized keys
3. **queryOptions everywhere reusable**: Prefer `queryOptions()` over ad hoc inline options whenever a query is used in more than one place (component, loader, prefetch)
4. **Type safety**: Define TypeScript interfaces for all API responses
5. **DevTools**: Always include React Query DevTools in development
6. **Avoid deeply nested queries**: Flatten query structures when possible
7. **Fetch only needed data**: Use API parameters to limit response size
8. **Handle loading and error states**: Always provide appropriate UI feedback

## Anti-Patterns to Avoid

- Do not use `useEffect` to fetch data — use queries or router loaders instead
- Do not store server state in local state (`useState`)
- Do not pass positional arguments to `useQuery`/`useInfiniteQuery` — v5 requires the options-object form
- Do not check `isLoading` alone on a mutation — use `isPending`
- Do not forget to handle loading and error states
- Do not create overly specific query keys that prevent cache reuse
- Do not skip cache invalidation after mutations
- Do not ignore the `enabled` option for conditional queries
- Do not define `queryOptions`/query configs inline inside components when they're reused elsewhere — co-locate and share them

© Mindrally · Apache-2.0 · Mindrally/skills

Похожие скиллы

Всё по теме «Фронтенд и UI» →

Сначала — tanstack-query от других авторов.

tanstack-query tanstack-skills Скилл подсказывает агенту, как работать с TanStack Query v5: кэширование, инвалидация, мутации и оптимистичные обновления. CX Codex +1 9 900установок tanstack-query-best-practices DeckardGer Свод правил TanStack Query для React: ключи запросов, кеш, мутации, SSR. Подключается к Claude Code, Codex, Cursor и Copilot — чтобы загрузка данных в проекте шла по одной схеме. CX Codex +1 12 800установок tanstack-query-v5-cursorrules-prompt-file PatrickJS Правило для Cursor, которое задаёт агенту стиль работы с TanStack Query v5: ключи запросов, мутации, оптимистичные обновления, Suspense и префетчинг. ★ 41 011звёзд репо react-data-fetching patterns.dev Учит агента правильной загрузке данных в React: параллельные запросы, кэш, дедупликация, Suspense. Пригодится, когда компоненты тянут данные через useEffect и fetch. CX Codex +1 1 100установок router-query TanStack Связывает TanStack Router с TanStack Query для предзагрузки данных в лоадерах, кэширования через useSuspenseQuery и изоляции QueryClient при SSR. CX Codex +1 366установок tanstack-router-best-practices DeckardGer Набор правил по TanStack Router для React: типизация, загрузка данных, search params и навигация. Пригодится, когда роутинг в проекте разрастается. CX Codex +1 7 100установок tanstack-router tanstack-skills Скилл по TanStack Router для React и Solid: файловый роутинг, поисковые параметры и загрузка данных с проверкой типов. CX Codex +1 4 900установок

Из того же набора168

sanity Скилл для работы с Sanity CMS: помогает описывать схемы документов, писать GROQ-запросы и связывать контент с TypeScript. CX Codex +1 725установок llm Скилл задаёт агенту правила работы с большими языковыми моделями: архитектура, файнтюнинг, обучение и деплой. Пригодится, когда проект крутится вокруг трансформеров. CX Codex +1 724установок dotnet Задаёт агенту правила C#, ASP.NET Core и Entity Framework Core: структура проекта, async/await, DI, кэш, тесты. CX Codex +1 724установок fastapi-microservices-serverless Скилл задаёт агенту правила проектирования FastAPI-микросервисов под serverless и cloud-native: без состояния, с Redis, API Gateway и OAuth2. CX Codex +1 724установок phoenix Задаёт агенту правила кода на Elixir и Phoenix: LiveView, Ecto, контексты, тесты. Пригодится, когда проект уже на Phoenix и нужен предсказуемый стиль. CX Codex +1 722установок solidity Скилл для написания и ревью Solidity-контрактов: паттерны защиты от reentrancy, оптимизация gas, тесты и связка с React-фронтендом. CX Codex +1 721установок autogen-development Подсказывает агенту, как писать код на Microsoft AutoGen: создание агентов, групповые чаты, инструменты, оркестрация. CX Codex +1 721установок vercel-development Набор правил для ИИ-агента: как писать код под Next.js App Router, React Server Components и Vercel AI SDK. Пригодится, когда агент лепит клиентские компоненты там, где хватило бы серверных. CX Codex +1 712установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой