К основному содержанию
правило

react-query — правило для Cursor: что делает и как установить

Правило задаёт структуру ключей кеша и рекомендует выносить запросы TanStack Query в пользовательские хуки. Пригодится для React-проектов на JavaScript или TypeScript.

Установить в Cursor · Вручную: положить файлы Все способы и платформы ↓
.cursor/rules/react-query.mdc
Открыть на GitHub

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

Правило охватывает ключи запросов, пользовательские хуки, функции загрузки, условные запросы и преобразование данных. Оно советует строить ключи как массивы, выносить логику useQuery и useMutation из компонентов, а для запросов с условиями применять enabled.

Для фильтрации и преобразования данных предлагается select, а функции запросов — отделять от компонентов. Файл настроен для **/*.{js,jsx,ts,tsx}. Это правило для Cursor, не готовая реализация запросов: агенту всё равно нужны конкретные API-функции и типы проекта.

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

  • организовать ключи запросов для кеша и фильтров
  • вынести повторяющуюся загрузку данных в пользовательский хук
  • запускать запрос только при наличии идентификатора
  • отфильтровать и преобразовать результат через select

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

  • Приведи запросы пользователей к правилу react-query: вынеси useQuery в пользовательские хуки.
  • Добавь условную загрузку профиля через enabled, когда userId задан.
  • Перенеси фильтрацию активных пользователей в select.

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

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

.cursor/rules/react-query.mdc

Во frontmatter: description, globs, alwaysApply

По шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules

Вопросы о react-query

Как установить react-query в Cursor?

Добавь файл правила по пути .cursor/rules/react-query.mdc.

Что делает react-query?

Правило описывает работу с TanStack Query: структуру ключей, пользовательские хуки и условную загрузку. Также оно рекомендует применять select для преобразования данных.

Для каких файлов настроено правило?

Оно применяется к файлам с расширениями .js, .jsx, .ts и .tsx.

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

react-query.mdc · CC0-1.0
---
description: Definitive guidelines for using TanStack Query (formerly React Query) to manage server state efficiently, ensure type safety, and optimize performance in React applications.
globs: **/*.{js,jsx,ts,tsx}
---
# TanStack Query (react-query) Best Practices

This document outlines the definitive best practices for using TanStack Query in our React applications. Adhering to these guidelines ensures consistent, performant, and maintainable data fetching and state management.

## 1. Query Keys: The Foundation of Caching

**ALWAYS** use stable, descriptive array keys. These are fundamental for caching, refetching, and invalidation. For dynamic data, embed parameters directly into the array.

### ✅ GOOD: Stable Array Keys & Key Factories

```typescript
// 1. Simple, static key
const USERS_KEY = ['users'];

// 2. Dynamic key with parameters
const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  list: (filters: { status?: string; page?: number }) =>
    [...userKeys.lists(), { filters }] as const,
  details: () => [...userKeys.all, 'detail'] as const,
  detail: (id: string) => [...userKeys.details(), id] as const,
};

// Usage:
// useQuery(userKeys.all, fetchAllUsers);
// useQuery(userKeys.list({ status: 'active', page: 1 }), fetchUsers);
// useQuery(userKeys.detail(userId), fetchUserById);
```

### ❌ BAD: Unstable or Generic Keys

```typescript
// String keys are less flexible for dynamic data and filtering
useQuery('users', fetchUsers);

// Anonymous object keys are unstable and break caching
useQuery(['users', { id: userId }], fetchUserById); // Object literal creates new reference each render
```

## 2. Custom Hooks: Encapsulate Logic

**ALWAYS** wrap `useQuery` and `useMutation` calls in custom hooks. This centralizes data fetching logic, improves reusability, enhances type safety, and keeps components clean.

### ✅ GOOD: Dedicated Custom Hooks

```typescript
// hooks/useUsers.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUsers, User } from '../api'; // Assume api.ts defines fetchUsers

const userKeys = {
  all: ['users'] as const,
  list: (filters: { status?: string }) => [...userKeys.all, { filters }] as const,
};

export function useUsers(filters?: { status?: string }) {
  return useQuery<User[], Error>({
    queryKey: userKeys.list(filters || {}),
    queryFn: () => fetchUsers(filters),
  });
}

// components/UserList.tsx
import { useUsers } from '../hooks/useUsers';

function UserList({ statusFilter }: { statusFilter?: string }) {
  const { data: users, isLoading, error } = useUsers({ status: statusFilter });

  if (isLoading) return <div>Loading users...</div>;
  if (error) return <div>Error: {error.message}</div>;

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

### ❌ BAD: Direct `useQuery` in Components

```typescript
// components/UserList.tsx
import { useQuery } from '@tanstack/react-query';
import { fetchUsers } from '../api';

function UserList({ statusFilter }: { statusFilter?: string }) {
  // Logic is duplicated if another component needs users
  // Query key is less organized
  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users', { status: statusFilter }],
    queryFn: () => fetchUsers({ status: statusFilter }),
  });

  // ... rest of component
}
```

## 3. Query Functions: Separate and Stable

**NEVER** pass anonymous functions directly to `queryFn`. **ALWAYS** declare `queryFn` separately to ensure stability, prevent unnecessary re-renders, and improve testability.

### ✅ GOOD: Separated Query Functions

```typescript
// api.ts
export async function fetchUserById(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) throw new Error('Failed to fetch user');
  return response.json();
}

// hooks/useUser.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUserById } from '../api';

const userKeys = {
  detail: (id: string) => ['users', id] as const,
};

export function useUser(userId: string) {
  return useQuery({
    queryKey: userKeys.detail(userId),
    queryFn: () => fetchUserById(userId), // Stable reference to fetchUserById
    enabled: !!userId, // Only run if userId exists
  });
}
```

### ❌ BAD: Anonymous Query Functions

```typescript
// hooks/useUser.ts
import { useQuery } from '@tanstack/react-query';

export function useUser(userId: string) {
  return useQuery({
    queryKey: ['users', userId],
    // This anonymous function is recreated on every render, potentially causing issues
    queryFn: async () => {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) throw new Error('Failed to fetch user');
      return response.json();
    },
    enabled: !!userId,
  });
}
```

## 4. Conditional Fetching: Use `enabled`

**ALWAYS** use the `enabled` option for conditional fetching. This is the explicit and recommended way to control when a query runs.

### ✅ GOOD: Using `enabled`

```typescript
// hooks/useUserProfile.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUserProfile } from '../api';

export function useUserProfile(userId?: string) {
  return useQuery({
    queryKey: ['userProfile', userId],
    queryFn: () => fetchUserProfile(userId!),
    enabled: !!userId, // Query only runs if userId is truthy
  });
}
```

### ❌ BAD: Conditional Hook Calls

```typescript
// components/UserProfile.tsx
import { useUserProfile } from '../hooks/useUserProfile';

function UserProfile({ userId }: { userId?: string }) {
  // React Hook Rules: Hooks must be called unconditionally
  // This breaks the rules and will cause bugs
  if (!userId) {
    return null;
  }
  const { data: user, isLoading } = useUserProfile(userId);
  // ...
}
```

## 5. Data Transformation: Use `select`

**ALWAYS** use the `select` option within `useQuery` for transforming or filtering data. This ensures the transformation happens once at the query level, optimizing performance and preventing redundant calculations in components.

### ✅ GOOD: `select` for Transformations

```typescript
// hooks/useActiveUsers.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUsers, User } from '../api';

export function useActiveUsers() {
  return useQuery<User[], Error, string[]>({ // Specify transformed data type
    queryKey: ['users', 'all'],
    queryFn: fetchUsers,
    select: (data) => data.filter(user => user.status === 'active').map(user => user.name),
  });
}

// components/ActiveUserNames.tsx
import { useActiveUsers } from '../hooks/useActiveUsers';

function ActiveUserNames() {
  const { data: activeUserNames, isLoading } = useActiveUsers();

  if (isLoading) return <div>Loading active users...</div>;
  return (
    <ul>
      {activeUserNames?.map((name) => (
        <li key={name}>{name}</li>
      ))}
    </ul>
  );
}
```

### ❌ BAD: Transforming Data in Every Component

```typescript
// components/ActiveUserNames.tsx
import { useQuery } from '@tanstack/react-query';
import { fetchUsers } from '../api';

function ActiveUserNames() {
  const { data: users, isLoading } = useQuery({
    queryKey: ['users', 'all'],
    queryFn: fetchUsers,
  });

  // Transformation logic repeated or inefficiently placed
  const activeUserNames = users?.filter(user => user.status === 'active').map(user => user.name);

  if (isLoading) return <div>Loading active users...</div>;
  return (
    <ul>
      {activeUserNames?.map((name) => (
        <li key={name}>{name}</li>
      ))}
    </ul>
  );
}
```

## 6. Mutations and Cache Invalidation

**ALWAYS** use `useMutation` for CUD (Create, Update, Delete) operations. After a successful mutation, **ALWAYS** invalidate relevant queries to ensure the UI reflects the latest server state. For immediate feedback, consider optimistic updates with `setQueryData`.

### ✅ GOOD: Invalidation after Mutation

```typescript
// hooks/useCreateTodo.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createTodo, Todo } from '../api';

export function useCreateTodo() {
  const queryClient = useQueryClient();
  return useMutation<Todo, Error, { title: string }>({
    mutationFn: createTodo,
    onSuccess: () => {
      // Invalidate all 'todos' queries to refetch fresh data
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });
}

// components/TodoForm.tsx
import { useCreateTodo } from '../hooks/useCreateTodo';

function TodoForm() {
  const { mutate, isLoading } = useCreateTodo();

  const handleSubmit = (event: React.FormEvent) => {
    event.preventDefault();
    const formData = new FormData(event.currentTarget as HTMLFormElement);
    const title = formData.get('title') as string;
    mutate({ title });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="New todo" />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Adding...' : 'Add Todo'}
      </button>
    </form>
  );
}
```

### ✅ GOOD: Optimistic Updates

```typescript
// hooks/useUpdateTodo.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { updateTodo, Todo } from '../api';

export function useUpdateTodo() {
  const queryClient = useQueryClient();
  return useMutation<Todo, Error, Partial<Todo> & { id: string }>({
    mutationFn: updateTodo,
    // Optimistically update the cache
    onMutate: async (newTodo) => {
      await queryClient.cancelQueries({ queryKey: ['todos'] });
      const previousTodos = queryClient.getQueryData<Todo[]>(['todos']);
      queryClient.setQueryData<Todo[]>(['todos'], (old) =>
        old ? old.map((todo) => (todo.id === newTodo.id ? { ...todo, ...newTodo } : todo)) : []
      );
      return { previousTodos }; // Context for onError
    },
    onError: (err, newTodo, context) => {
      // Rollback on error
      queryClient.setQueryData(['todos'], context?.previousTodos);
    },
    onSettled: () => {
      // Always refetch after error or success to ensure data is in sync
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });
}
```

## 7. Performance: Prefetching & Defaults

**LEVERAGE** TanStack Query's defaults (e.g., `staleTime: 0`, automatic retries, refetch on window focus) and **STRATEGICALLY** use prefetching for critical user flows.

### ✅ GOOD: Prefetching for Router Integration

```typescript
// utils/routeLoaders.ts (Example with a router loader)
import { QueryClient } from '@tanstack/react-query';
import { fetchProjectById } from '../api';

export const projectLoader = (queryClient: QueryClient) => async ({ params }: { params: { projectId: string } }) => {
  const queryKey = ['projects', params.projectId];
  // Prefetch the project data during navigation
  await queryClient.prefetchQuery({
    queryKey,
    queryFn: () => fetchProjectById(params.projectId),
  });
  return null; // Or return initial data if needed
};

// components/ProjectLink.tsx
import { Link } from 'react-router-dom'; // Assuming react-router-dom
import { useQueryClient } from '@tanstack/react-query';
import { fetchProjectById } from '../api';

function ProjectLink({ projectId, projectName }: { projectId: string; projectName: string }) {
  const queryClient = useQueryClient();
  const handleMouseEnter = () => {
    // Prefetch on hover for instant page loads
    queryClient.prefetchQuery({
      queryKey: ['projects', projectId],
      queryFn: () => fetchProjectById(projectId),
      staleTime: 5 * 60 * 1000, // Keep data fresh for 5 minutes
    });
  };

  return (
    <Link to={`/projects/${projectId}`} onMouseEnter={handleMouseEnter}>
      {projectName}
    </Link>
  );
}
```

## 8. ESLint Plugin: Enforce Standards

**ALWAYS** install and configure the `@tanstack/query-eslint-plugin`. It enforces many of these best practices automatically, catching common mistakes early.

```json
// .eslintrc.json
{
  "plugins": ["@tanstack/query"],
  "rules": {
    "@tanstack/query/exhaustive-deps": "error",
    "@tanstack/query/prefer-query-object": "error",
    "@tanstack/query/stable-query-client": "error"
  }
}
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Похожие правила IDE

Все правила IDE по теме «Фронтенд и UI» →
syncfusion-react-query-builder Syncfusion Inc Помогает внедрить визуальный конструктор фильтров Syncfusion в React-приложения. Генерирует интерфейсы правил и переводит условия в SQL или Mongo. CX Codex +1 708установок react-tanstack-router-query PatrickJS Настраивает связку TanStack Router v1 и TanStack Query v5 в React-проектах для загрузки данных в роутере без спиннеров ожидания. ★ 41 028звёзд репо react-tanstack-router-query-cursorrules-prompt-file PatrickJS Правило задаёт схему для React SPA: TanStack Router управляет маршрутами, а TanStack Query — серверными данными и кэшем. Пригодится, когда данные маршрута нужно загрузить до показа страницы. ★ 41 028звёзд репо tanstack-query-best-practices DeckardGer Свод правил TanStack Query для React: ключи запросов, кеш, мутации, SSR. Подключается к Claude Code, Codex, Cursor и Copilot — чтобы загрузка данных в проекте шла по одной схеме. CX Codex +1 12 800установок tanstack-query tanstack-skills Скилл подсказывает агенту, как работать с TanStack Query v5: кэширование, инвалидация, мутации и оптимистичные обновления. CX Codex +1 9 900установок safe-action-tanstack-query next-safe-action Помогает связывать next-safe-action с TanStack Query: мутации, ошибки, optimistic updates и очистка клиентского кэша. Нужен при работе с React Query. CX Codex +1 438установок

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

Весь набор →
redux Задаёт стандарты Redux Toolkit в Cursor: файловую структуру слайсов, нормализацию данных и запросы через RTK Query. ★ 3 580звёзд репо redis Правило задаёт практики работы с Redis: от структуры ключей до подключения к кластеру. Пригодится, чтобы агент учитывал эти решения при работе с кодом. ★ 3 580звёзд репо react Правило для Cursor задаёт команде единые стандарты React: чистые компоненты, правила хуков, структура по фичам. Пригодится, когда в проекте несколько разработчиков и ИИ-агент пишет код вразнобой. ★ 3 580звёзд репо react-redux Направляет Cursor на использование Redux Toolkit, хуков и RTK Query в коде React вместо устаревшего connect и ручных редьюсеров. ★ 3 580звёзд репо react-native Правило задаёт обязательные практики для React Native: feature-first структуру, функциональные компоненты, хуки и типизацию. Пригодится, когда проект растёт и файлы расползаются. ★ 3 580звёзд репо react-mobx Задаёт стандарты реактивного состояния на MobX: запрещает устаревшие декораторы и настраивает RootStore через React Context. ★ 3 580звёзд репо railway Правило помогает настроить приложение для Railway: от структуры монорепозитория и секретов до проверок состояния и завершения работы сервиса. ★ 3 580звёзд репо qwik Правило Cursor, которое подсказывает агенту, как писать Qwik-приложения: component$, useStore, QRL с суффиксом $ и загрузка данных через loader$. ★ 3 580звёзд репо

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

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