Skill: tanstack-query
SkillDev toolsTanStack Query (React Query) for async operations, data fetching, caching, and state management. Use when fetching server data, managing async operations, caching responses, handling mutations, or any operation that benefits from automatic state management and caching.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Skill: tanstack-query skill
What this skill tells your AI
The instructions your AI receives, as published by blockmatic/basilic in .agents/skills/tanstack-query-v5/SKILL.md and read by ahel’s review.
Scope
- Applies to: TanStack Query v5+ for async operations, data fetching, caching, mutations, infinite queries, optimistic updates
- Does NOT cover: URL state management (use nuqs), grouped synchronous state (use ahooks.useSetState), localStorage persistence (use ahooks.useLocalStorageState)
Assumptions
- TanStack Query v5+
- React 18+ with hooks support
- TypeScript v5+ (for type inference)
- Handwritten query-key tuples in shared query modules (query-key-factory is optional)
- QueryClientProvider configured at app root
Principles
- Use TanStack Query for ANY async operation that benefits from caching or state management
- Prefer handwritten query-key tuples colocated with hooks or in a shared
queries/module - Never manually manage
isLoading,error, orisErrorstates (provided by hooks) queryFncan be any Promise-returning function (not just HTTP calls)- All TanStack features work identically regardless of data source: caching, deduping, background refetching, stale-while-revalidate
Constraints
MUST
- Use stable, hierarchical query keys (handwritten tuples or optional query-key-factory)
- Use TanStack Query hooks for async operations (never manually manage loading/error states)
SHOULD
- Use TanStack Query for any async operation that benefits from caching (HTTP, localStorage reads, local computations, file operations)
- Extract query logic into custom hooks for reusability
- Combine multiple queries into cohesive hooks
- Use TypeScript generics for type-safe queries:
useQuery<ResponseType>(...) - Configure QueryClient defaults (retry, staleTime) at provider level
- Use
@lukemorales/query-key-factorywhen the project already standardizes on it
AVOID
- Hardcoding unrelated query keys in invalidations without a shared prefix
- Manually managing loading/error states (use hook-provided states)
- Using for URL-shareable state (use nuqs instead)
- Using for grouped synchronous state (use ahooks.useSetState instead)
- Using for one-off promises without caching needs (use plain async/await in event handlers)
Interactions
- Complements nuqs-v2 for URL state management (queries can depend on URL params)
- Complements ahooks-v3 for synchronous state (useSetState for form state, useLocalStorageState for persistence)
- Works with OpenAPI-generated API clients; React Query hooks are handwritten against those clients
- Part of state management decision tree (see React rules)
Patterns
Handwritten Query Keys
Colocated, stable query keys:
export const userKeys = {
all: ['users'] as const,
detail: (id: string) => [...userKeys.all, id] as const,
list: (filters?: Filters) => [...userKeys.all, 'list', filters] as const,
}
// Usage
import { useQuery } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
const { data, isLoading, error } = useQuery({
queryKey: userKeys.detail(userId),
queryFn: () => fetchUser(userId),
})
Optional Query Key Factory
When the project already uses @lukemorales/query-key-factory:
import { createQueryKeys } from '@lukemorales/query-key-factory'
export const users = createQueryKeys('users', {
detail: (id: string) => ({
queryKey: [id],
queryFn: () => fetchUser(id),
}),
})
Versatile queryFn Pattern
TanStack Query accepts ANY async queryFn—not just HTTP calls:
// Server data fetching (traditional)
const { data } = useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
})
// Local computation (no network)
const { data } = useQuery({
queryKey: ['fibonacci', n],
queryFn: () => computeFibonacci(n),
})
// localStorage read
const { data: settings } = useQuery({
queryKey: ['user-settings'],
queryFn: () => Promise.resolve(
JSON.parse(localStorage.getItem('settings') || '{}')
),
})
// File operation
const { data } = useQuery({
queryKey: ['file-content', path],
queryFn: () => readFile(path),
})
When to use: Any async operation that benefits from automatic caching, stale management, or repeated execution
When NOT to use: One-off promises without caching needs (use plain async/await in event handlers)
Mutation Pattern
Mutations with automatic invalidation:
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
function useCreateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (data: CreateUserInput) => {
const response = await createUser(data)
return response
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: userKeys.all })
},
})
}
Infinite Query Pattern
Pagination with infinite scroll:
import { useInfiniteQuery } from '@tanstack/react-query'
import { users } from '@/queries/users'
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['users', 'infinite'],
queryFn: ({ pageParam = 0 }) => fetchUsers({ page: pageParam }),
getNextPageParam: (lastPage, pages) => lastPage.nextPage,
initialPageParam: 0,
})
QueryClient Configuration
Configure defaults at provider level:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
function Providers({ children }: { children: ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 1,
refetchOnWindowFocus: false,
},
},
}))
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
Handwritten Hook Pattern
Wrap the generated OpenAPI client in custom hooks:
import { useQuery } from '@tanstack/react-query'
import { apiClient } from '@/lib/api-client'
export function useHealthCheck() {
return useQuery({
queryKey: ['health'],
queryFn: () => apiClient.GET('/health').then(r => r.data),
})
}
Custom Hook Pattern
Extract query logic into reusable hooks:
// hooks/useUser.ts
import { useQuery } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
export function useUser(userId: string) {
return useQuery({
queryKey: userKeys.detail(userId),
queryFn: () => fetchUser(userId),
})
}
State Management Decision Tree
- URL-shareable state → Use
nuqs(filters, search, tabs, pagination) - Grouped state not in URL → Use
ahooks.useSetState(form state, game engine, ephemeral UI) - Async operations → Use TanStack Query (data fetching, mutations, caching)
- localStorage persistence → Use
ahooks.useLocalStorageState(preferences, settings) - Simple independent state → Use
useState(rare, prefer other options)
Use TanStack Query for: Server data fetching, mutations, optimistic updates, background refetching, caching, any async operation that benefits from state management
Don't use TanStack Query for: URL-shareable state (use nuqs), grouped synchronous state (use ahooks.useSetState), one-off promises without caching needs
References
- TanStack Query documentation - Official documentation
- Query Key Factory - Query key factory library
- React rules - State management decision tree and patterns
- ahooks - Utility hooks for synchronous state
- OpenAPI Integration - Generated client patterns
Signals
- GitHub stars
- 89
- Forks
- 11
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
tanstack-query-v5- Source
- github.com/blockmatic/basilic