Skip to main content
skill CX Codex

react-query — Claude Code skill: React Query rules for your agent

Tells the agent how to fetch and cache server data in React: queries, mutations, invalidation and optimistic updates.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add Mindrally/skills --skill react-query -a claude-code
Open on GitHub

What react-query does

The skill teaches the agent how React Query (now TanStack Query) works and keeps it writing code that way. Server data goes through useQuery and useMutation instead of useState. Client state stays with React Context and useReducer, or with Zustand — the skill shows both.

Inside you get a project layout with queries.ts and mutations.ts next to each feature, a QueryClientProvider setup with staleTime, cacheTime and retry, plus patterns for dependent queries, pagination and infinite scroll via useInfiniteQuery. Optimistic updates have their own section: cancel the query, keep the previous value, roll back in onError. Query keys are meant to live in one queryKeys object with factories rather than inline strings.

The examples import from react-query, not @tanstack/react-query. On TanStack Query v5 they break: cacheTime is gone and keepPreviousData became placeholderData. Check which version your project runs before letting the agent copy anything.

What it helps with

  • separate server state from client state in a React app
  • set up caching and background refetching without hand-written useEffect
  • do an optimistic update that rolls back on failure
  • build pagination and infinite scroll with useInfiniteQuery
  • keep query keys consistent through a key factory

Example prompts for the agent

  • set up React Query in this project and move query hooks into their own files
  • add an optimistic update for editing the user profile
  • rewrite the user list loading with useInfiniteQuery

How to install react-query

1Skills CLI (Vercel)

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

2Manually: put the files in place

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

Copy the whole skill folder; for one project use .claude/skills/react-query/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about react-query

How do I install react-query in Claude?

Run npx skills add Mindrally/skills --skill react-query -a claude-code. The skill lands in ~/.claude/skills/react-query/SKILL.md.

What does the react-query skill do?

It gives the agent rules for React Query: queries, mutations, caching, invalidation and optimistic updates. The agent follows them whenever it writes data-fetching code in React.

Which React Query versions does the skill cover?

The examples use the react-query package with the older API: cacheTime, keepPreviousData. For TanStack Query v5 they need rewriting.

Source text

SKILL.md · Apache-2.0
---
name: react-query
description: Guidelines for using React Query for data fetching, caching, and server state synchronization in React applications
metadata:
  maintainer: Mindrally
  source: https://github.com/Mindrally/skills
---

# React Query Best Practices

You are an expert in React Query, TypeScript, and React development. React Query (now TanStack Query) simplifies data fetching logic with built-in caching, background updates, and stale data management.

## Core Principles

- Use React Query for all data fetching and caching
- Leverage React Query's built-in state management instead of `useState` for server data
- Use React Context and `useReducer` for managing client-side global state
- Avoid excessive API calls through proper caching strategies
- Always handle loading states and errors properly

## Project Structure

```
src/
  components/
    [Feature]/
      index.tsx
      queries.ts           # Feature-specific query hooks
      mutations.ts         # Feature-specific mutation hooks
  hooks/
    useAuth.ts
    useApi.ts
  services/
    api/
      client.ts            # Axios/fetch configuration
      users.ts             # User API functions
      posts.ts             # Post API functions
  providers/
    ReactQueryProvider.tsx
  types/
    index.ts
```

## Setup

### Provider Configuration

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

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,    // 5 minutes
      cacheTime: 30 * 60 * 1000,   // 30 minutes
      retry: 2,
      refetchOnWindowFocus: true,
    },
  },
});

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

## Query Patterns

### Basic Query Hook

```typescript
import { useQuery } from 'react-query';
import { fetchUser, User } from '@/services/api/users';

export function useUser(userId: string) {
  return useQuery<User, Error>(
    ['user', userId],
    () => fetchUser(userId),
    {
      enabled: !!userId,
      staleTime: 1000 * 60 * 10, // 10 minutes
    }
  );
}
```

### Query with Error Handling

Services should throw user-friendly errors that React Query can catch and display:

```typescript
// services/api/users.ts
export async function fetchUser(userId: string): Promise<User> {
  const response = await fetch(`/api/users/${userId}`);

  if (!response.ok) {
    // Throw user-friendly error message
    throw new Error('Unable to load user profile. Please try again.');
  }

  return response.json();
}

// Component usage
function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading, error } = useUser(userId);

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error.message} />;

  return <ProfileCard user={user} />;
}
```

### Dependent Queries

```typescript
function useUserWithPosts(userId: string) {
  const userQuery = useUser(userId);

  const postsQuery = useQuery(
    ['posts', userId],
    () => fetchUserPosts(userId),
    {
      enabled: !!userQuery.data,
    }
  );

  return { userQuery, postsQuery };
}
```

### Paginated Queries

```typescript
function usePaginatedUsers(page: number, limit: number = 10) {
  return useQuery(
    ['users', 'list', { page, limit }],
    () => fetchUsers({ page, limit }),
    {
      keepPreviousData: true,
    }
  );
}
```

### Infinite Scroll

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

function useInfiniteUsers() {
  return useInfiniteQuery(
    ['users', 'infinite'],
    ({ pageParam = 1 }) => fetchUsers({ page: pageParam }),
    {
      getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
    }
  );
}
```

## Mutation Patterns

### Basic Mutation

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

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

  return useMutation(createUser, {
    onSuccess: () => {
      queryClient.invalidateQueries(['users']);
    },
    onError: (error: Error) => {
      toast.error(error.message);
    },
  });
}
```

### Optimistic Updates

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

  return useMutation(updateUser, {
    onMutate: async (updatedUser) => {
      await queryClient.cancelQueries(['user', updatedUser.id]);

      const previousUser = queryClient.getQueryData(['user', updatedUser.id]);

      queryClient.setQueryData(['user', updatedUser.id], updatedUser);

      return { previousUser };
    },
    onError: (err, updatedUser, context) => {
      if (context?.previousUser) {
        queryClient.setQueryData(['user', updatedUser.id], context.previousUser);
      }
    },
    onSettled: (data, error, updatedUser) => {
      queryClient.invalidateQueries(['user', updatedUser.id]);
    },
  });
}
```

## State Management Integration

### Combining with Context/Reducer

Use React Query for server state and Context/Reducer for client state:

```typescript
// Client state with Context
const AppStateContext = createContext<AppState | undefined>(undefined);
const AppDispatchContext = createContext<Dispatch<Action> | undefined>(undefined);

function AppProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(appReducer, initialState);

  return (
    <AppStateContext.Provider value={state}>
      <AppDispatchContext.Provider value={dispatch}>
        {children}
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  );
}

// Server state with React Query
function UserDashboard() {
  const { theme } = useAppState();         // Client state
  const { data: user } = useUser(userId);  // Server state

  return <Dashboard theme={theme} user={user} />;
}
```

### Combining with Zustand (Alternative)

```typescript
import { create } from 'zustand';

// Client state store
const useStore = create((set) => ({
  theme: 'light',
  setTheme: (theme) => set({ theme }),
}));

// Component using both
function App() {
  const theme = useStore((state) => state.theme);
  const { data: user } = useUser(userId);

  return <Layout theme={theme} user={user} />;
}
```

## Performance Optimization

### Query Key Best Practices

```typescript
// Structured query keys
const queryKeys = {
  users: {
    all: ['users'] as const,
    lists: () => [...queryKeys.users.all, 'list'] as const,
    list: (filters: Filters) => [...queryKeys.users.lists(), filters] as const,
    details: () => [...queryKeys.users.all, 'detail'] as const,
    detail: (id: string) => [...queryKeys.users.details(), id] as const,
  },
};
```

### Selective Subscriptions

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

### Prefetching

```typescript
function UserListItem({ userId }: { userId: string }) {
  const queryClient = useQueryClient();

  const handleMouseEnter = () => {
    queryClient.prefetchQuery(
      ['user', userId],
      () => fetchUser(userId),
      { staleTime: 60000 }
    );
  };

  return (
    <li onMouseEnter={handleMouseEnter}>
      <Link to={`/users/${userId}`}>View Profile</Link>
    </li>
  );
}
```

## Error Handling Patterns

### Global Error Handler

```typescript
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      onError: (error: Error) => {
        console.error('Query error:', error);
      },
    },
    mutations: {
      onError: (error: Error) => {
        toast.error(error.message);
      },
    },
  },
});
```

### Error Boundaries

```typescript
import { QueryErrorResetBoundary } from 'react-query';
import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    <QueryErrorResetBoundary>
      {({ reset }) => (
        <ErrorBoundary
          onReset={reset}
          fallbackRender={({ error, resetErrorBoundary }) => (
            <div>
              <p>Something went wrong: {error.message}</p>
              <button onClick={resetErrorBoundary}>Try again</button>
            </div>
          )}
        >
          <UserProfile />
        </ErrorBoundary>
      )}
    </QueryErrorResetBoundary>
  );
}
```

## Key Conventions

1. Use React Query DevTools to inspect cache and track query status
2. Group react-query hooks within feature-specific directories (feature-based organization)
3. Always handle errors properly with user-friendly messages and retry options
4. Fetch only required data - use API parameters to reduce data transfer
5. Avoid deeply nesting queries - flatten when possible for better performance
6. Use local state for component-specific data, global state for shared data
7. Leverage React Query's built-in caching and state management capabilities

## Anti-Patterns to Avoid

- Do not use `useEffect` for data fetching
- Do not store server data in `useState`
- Do not forget loading and error state handling
- Do not create queries without proper cache invalidation strategies
- Do not skip the `enabled` option for conditional queries
- Do not ignore TypeScript types for query responses

© Mindrally · Apache-2.0 · Mindrally/skills

Similar skills

Everything for Frontend and UI →

From the same set168

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor