Back to skills
SKILL.md
Tanstack Query Expert
ASecurityExpert 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
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill tanstack-query-expert --agent claude-codeAre you the author of Tanstack Query Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-tanstack-query-expert)---
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
Comments
Loading comments…