tanstack query
via PatrickJS/awesome-cursorrules
TanStack Query v5 patterns: queryOptions, key factories, mutations, optimistic updates, and Suspense mode.
What is tanstack query?
Establishes best practices for TanStack Query v5 (React Query) as a server-state management library in TypeScript. Use this rule when building data-fetching layers with queries, mutations, infinite queries, and prefetching in React applications.
- Define reusable query configurations with queryOptions helper and query key factories
- Implement mutations with onSuccess/onError callbacks and query invalidation
- Build optimistic updates with onMutate rollback and onSettled settlement
- Handle infinite queries with cursor-based pagination and getNextPageParam
- Use Suspense mode (useSuspenseQuery) with ErrorBoundary for loading states
- Configure QueryClient defaults for staleTime, retry logic, and prefetching in loaders
Applies to
File patterns this rule matches.
Rule definition (reference)
Source of truth, from the repository.
You are an expert in TanStack Query v5 (React Query), TypeScript, and async state management.
Core Principles
- TanStack Query manages server state — NOT a general client state manager
- Every query needs a stable, serializable query key that uniquely describes the data
- Mutations handle writes; queries handle reads — never blur this boundary
- Use
queryOptions()helper (v5) for reusable, co-located query definitions - v5 breaking change:
useQueryonly accepts options object form — no positional args
QueryClient Setup
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60,
retry: (count, error: any) => error?.status !== 404 && count < 2,
},
},
})
Query Key Factory Pattern
export const postKeys = {
all: ['posts'] as const,
lists: () => [...postKeys.all, 'list'] as const,
list: (filters?: PostFilters) => [...postKeys.lists(), filters] as const,
details: () => [...postKeys.all, 'detail'] as const,
detail: (id: string) => [...postKeys.details(), id] as const,
}
queryOptions Helper (v5)
export const postQueryOptions = (id: string) =>
queryOptions({
queryKey: postKeys.detail(id),
queryFn: () => fetchPost(id),
staleTime: 1000 * 60 * 5,
})
// In component
const { data } = useQuery(postQueryOptions(postId))
// In router loader
loader: ({ params, context: { queryClient } }) =>
queryClient.ensureQueryData(postQueryOptions(params.postId))
Mutations
const { mutate, isPending } = useMutation({
mutationFn: (input: CreatePostInput) => createPost(input),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: postKeys.lists() })
},
onError: (error) => toast.error(error.message),
})
Optimistic Updates
const mutation = useMutation({
mutationFn: updatePost,
onMutate: async (updated) => {
await queryClient.cancelQueries({ queryKey: postKeys.detail(updated.id) })
const previous = queryClient.getQueryData(postKeys.detail(updated.id))
queryClient.setQueryData(postKeys.detail(updated.id), updated)
return { previous }
},
onError: (_, updated, ctx) => {
queryClient.setQueryData(postKeys.detail(updated.id), ctx?.previous)
},
onSettled: (_, __, updated) => {
queryClient.invalidateQueries({ queryKey: postKeys.detail(updated.id) })
},
})
Infinite Queries
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: postKeys.lists(),
queryFn: ({ pageParam }) => fetchPosts({ cursor: pageParam }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
const allPosts = data?.pages.flatMap((p) => p.items) ?? []
Suspense Mode (v5)
// useSuspenseQuery — no isLoading needed, Suspense handles it
const { data } = useSuspenseQuery(postQueryOptions(postId))
// Wrap with <Suspense fallback={<Skeleton />}> + <ErrorBoundary>
Key Rules
- Always define
queryOptionsoutside components — never inline inuseQuery() - Never use
useEffectto fetch data — use loaders oruseQuery - Use
placeholderData: keepPreviousDatafor pagination to avoid layout shifts - Instantiate
QueryClientonce at app root — never inside a component
Related rules
Expert guidance for TanStack Query v5: queries, mutations, optimistic updates, infinite queries, and Suspense patterns.
Type-safe file-based routing for React with TanStack Router v1, loaders, and search params validation.
Type-safe file-based routing for React with TanStack Router v1, loaders, and search validation.
Full-stack React framework with type-safe server functions, file-based routing, and streaming via TanStack Router + Vinxi.
Full-stack React framework with server functions, streaming, and end-to-end type safety.
Expert guidance for building desktop apps with Tauri, Svelte, and TypeScript.
