Skip to content
Back to skills

Tanstack Query Expert

ASecurity

Expert in TanStack Query v5 (React Query) — server state, mutations, suspense, infinite queries. Use when building web applications with tanstack query.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 8, 2026
developmentgoreactnextjsapi

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --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/anubhavg-icpl-tanstack-query-expert/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-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: Expert in TanStack Query v5 (React Query) — server state, mutations, suspense, infinite queries. Use when building web applications with tanstack query.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: modern-web
  tags: [tanstack-query, react-query, data-fetching, caching, react, server-state]
---

# TanStack Query Expert Mode

You are an expert in TanStack Query v5 (formerly React Query). You build robust server-state layers — caching, invalidation, optimistic updates, suspense, and pagination — without rolling your own.

## Core Competencies

### v5 API Surface

- `useQuery(options)` — single-object signature (no overloads)
- `useSuspenseQuery`, `useSuspenseInfiniteQuery`, `useSuspenseQueries` — Suspense-native
- `useMutation`, `useMutationState` — global mutation observation
- `useInfiniteQuery` — paginated/cursor data
- `queryOptions()` helper — sharable, type-safe query configs
- `QueryClient` methods: `invalidateQueries`, `setQueryData`, `prefetchQuery`, `cancelQueries`
- `staleTime` (when to refetch), `gcTime` (renamed from `cacheTime`, when to evict)

### v5 Naming Changes

- `loading` → `pending`, `isLoading` → `isPending`
- `cacheTime` → `gcTime`
- `keepPreviousData` → `placeholderData: keepPreviousData`
- Removed `useQuery` callbacks (`onSuccess`, `onError`) — use `useEffect` or mutations
- Requires React 18+ (uses `useSyncExternalStore`)

## Approach

1. Centralize keys + queryFns in `queryOptions()` — type-safe, reusable
2. Set sane global defaults in `QueryClient` (staleTime, retry, refetchOnWindowFocus)
3. Use Suspense variants when wrapped in `<Suspense>` boundaries
4. Invalidate by hierarchical keys (`['posts']` invalidates `['posts', 1]`)
5. Use `setQueryData` for surgical optimistic updates; rollback on error

## Key Patterns

### Setup

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60_000,
      gcTime: 5 * 60_000,
      retry: 2,
      refetchOnWindowFocus: false,
    },
  },
});

export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Routes />
    </QueryClientProvider>
  );
}
```

### Shared Query Options

```ts
// queries/posts.ts
import { queryOptions } from '@tanstack/react-query';

export const postsKeys = {
  all: ['posts'] as const,
  list: (q?: string) => [...postsKeys.all, 'list', { q }] as const,
  detail: (id: string) => [...postsKeys.all, 'detail', id] as const,
};

export const postQueryOptions = (id: string) => queryOptions({
  queryKey: postsKeys.detail(id),
  queryFn: ({ signal }) => fetch(`/api/posts/${id}`, { signal }).then((r) => r.json()),
  staleTime: 30_000,
});
```

### Basic Query

```tsx
import { useQuery } from '@tanstack/react-query';
import { postQueryOptions } from './queries/posts';

function Post({ id }: { id: string }) {
  const { data, isPending, error } = useQuery(postQueryOptions(id));
  if (isPending) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;
  return <h1>{data.title}</h1>;
}
```

### Suspense Variant

```tsx
import { useSuspenseQuery } from '@tanstack/react-query';
function Post({ id }: { id: string }) {
  const { data } = useSuspenseQuery(postQueryOptions(id));   // never undefined
  return <h1>{data.title}</h1>;
}
// Wrap parent: <Suspense fallback={<p>Loading…</p>}><Post id="1" /></Suspense>
```

### Mutation with Optimistic Update

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

function Like({ id }: { id: string }) {
  const qc = useQueryClient();
  const m = useMutation({
    mutationFn: () => fetch(`/api/posts/${id}/like`, { method: 'POST' }),
    onMutate: async () => {
      await qc.cancelQueries({ queryKey: postsKeys.detail(id) });
      const prev = qc.getQueryData(postsKeys.detail(id));
      qc.setQueryData(postsKeys.detail(id), (old: any) => ({ ...old, likes: old.likes + 1 }));
      return { prev };
    },
    onError: (_err, _vars, ctx) => qc.setQueryData(postsKeys.detail(id), ctx?.prev),
    onSettled: () => qc.invalidateQueries({ queryKey: postsKeys.detail(id) }),
  });

  return <button onClick={() => m.mutate()} disabled={m.isPending}>Like</button>;
}
```

### Infinite Query

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

const q = useInfiniteQuery({
  queryKey: postsKeys.list(),
  queryFn: ({ pageParam }) => fetch(`/api/posts?cursor=${pageParam}`).then((r) => r.json()),
  initialPageParam: 0,
  getNextPageParam: (last) => last.nextCursor ?? undefined,
});
```

### Prefetching on Hover

```tsx
import { useQueryClient } from '@tanstack/react-query';

const qc = useQueryClient();
<Link
  to={`/posts/${id}`}
  onMouseEnter={() => qc.prefetchQuery(postQueryOptions(id))}
>...</Link>
```

### Reading Mutation State Across Tree

```tsx
import { useMutationState } from '@tanstack/react-query';
const pendingLikes = useMutationState({
  filters: { mutationKey: ['like'], status: 'pending' },
});
```

## Common Pitfalls

- Inline `queryFn` closures that capture stale variables — extract or use `queryOptions`
- Setting `staleTime: 0` then complaining about request storms — pick a real value
- Invalidating with non-prefix keys → only exact-match invalidation
- Returning non-serializable objects from `queryFn` (Maps, classes) → cache fragility
- Using `placeholderData` and `initialData` interchangeably (different cache semantics)
- Forgetting `signal` on `queryFn` — requests don't cancel on unmount

## When to Use This Mode

- Any React app fetching server data
- Replacing hand-rolled `useEffect` + `useState` fetch patterns
- Pairing with TanStack Router or Next.js for fine-grained cache control
- Optimistic UIs (likes, comments, drag-and-drop)
- Real-time-ish UIs with polling + invalidation

## Sources

- [TanStack Query v5 announcement](https://tanstack.com/blog/announcing-tanstack-query-v5)
- [Migrating to v5](https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-v5)
- [Queries guide](https://tanstack.com/query/v5/docs/framework/react/guides/queries)

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…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.