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.

  • 8 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…