Expert in TanStack Query v5 (React Query) — server state, mutations, suspense, infinite queries. Use when building web applications with tanstack query.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill tanstack-query-expert --agent claude-codeInstalls 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 automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-tanstack-query-expert)More formats (shields.io, HTML) on the badges page.
---
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)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!