Tanstack Query V5
Cursor-Regel speziell für Tanstack Query v5 inklusive dessen Breaking Changes gegenüber v4.
Cursor-Regel speziell für Tanstack Query v5 inklusive dessen Breaking Changes gegenüber v4.
Original-Beschreibung der Autoren: Cursor rules for TanStack Query v5 with query options, query key factories, mutations, optimistic updates, infinite queries, Suspense, and prefetching.
Die Regel
---
description: "Cursor rules for TanStack Query v5 with query options, query key factories, mutations, optimistic updates, infinite queries, Suspense, and prefetching."
globs: **/*
alwaysApply: false
---
You are an expert in TanStack Query v5 (formerly React Query), TypeScript, and async state management for React applications.
# TanStack Query v5 Guidelines
## Core Philosophy
- TanStack Query manages server state — it is NOT a general state manager for client-only state
- Every query should have a stable, serializable query key that uniquely describes the data
- Mutations handle writes; queries handle reads — never blur this boundary
- Prefer `queryOptions()` helper for reusable, co-located query definitions
- v5 breaking changes: `useQuery` no longer accepts positional args; always use the options object form
## Setup
```tsx
// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60, // 1 minute default stale time
retry: 2,
refetchOnWindowFocus: true,
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
Query Keys
- Always structure keys as arrays:
['entity', 'list'],['entity', 'detail', id] - Use a query key factory to avoid typos and enable easy invalidation
// queryKeys.ts
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)
- Use
queryOptions()to define queries once and reuse across components and loaders
import { queryOptions } from '@tanstack/react-query'
export const postQueryOptions = (id: string) =>
queryOptions({
queryKey: postKeys.detail(id),
queryFn: () => fetchPost(id),
staleTime: 1000 * 60 * 5, // 5 min
})
// In component
const { data } = useQuery(postQueryOptions(postId))
// In router loader (TanStack Router integration)
loader: ({ params, context: { queryClient } }) =>
queryClient.ensureQueryData(postQueryOptions(params.postId))
useQuery
const {
data,
isLoading, // true only on first load with no cached data
isFetching, // true whenever a fetch is in-flight
isError,
error,
isSuccess,
} = useQuery({
queryKey: postKeys.detail(postId),
queryFn: () => fetchPost(postId),
enabled: !!postId, // disable query if params not ready
})
useMutation
const { mutate, mutateAsync, isPending } = useMutation({
mutationFn: (newPost: CreatePostInput) => createPost(newPost),
onSuccess: (data) => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: postKeys.lists() })
toast.success('Post created!')
},
onError: (error) => {
toast.error(error.message)
},
})
// Usage
mutate({ title: 'Hello', body: '...' })
Optimistic Updates
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: updatePost,
onMutate: async (updatedPost) => {
await queryClient.cancelQueries({ queryKey: postKeys.detail(updatedPost.id) })
const previous = queryClient.getQueryData(postKeys.detail(updatedPost.id))
queryClient.setQueryData(postKeys.detail(updatedPost.id), updatedPost)
return { previous }
},
onError: (err, updatedPost, context) => {
queryClient.setQueryData(postKeys.detail(updatedPost.id), context?.previous)
},
onSettled: (_, __, updatedPost) => {
queryClient.invalidateQueries({ queryKey: postKeys.detail(updatedPost.id) })
},
})
Infinite Queries
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: postKeys.lists(),
queryFn: ({ pageParam }) => fetchPosts({ cursor: pageParam }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
// data.pages is an array of page results — flatten for rendering
const allPosts = data?.pages.flatMap((page) => page.items) ?? []
Prefetching
- Prefetch on hover or during routing to eliminate loading states
// Hover prefetch
const handleMouseEnter = () => {
queryClient.prefetchQuery(postQueryOptions(postId))
}
// In router loader (eliminates all loading spinners)
export const Route = createFileRoute('/posts/$postId')({
loader: ({ context: { queryClient }, params }) =>
queryClient.ensureQueryData(postQueryOptions(params.postId)),
})
Cache Invalidation Patterns
// Invalidate all post queries
queryClient.invalidateQueries({ queryKey: postKeys.all })
// Invalidate only post lists
queryClient.invalidateQueries({ queryKey: postKeys.lists() })
// Remove from cache entirely
queryClient.removeQueries({ queryKey: postKeys.detail(id) })
// Directly update cache without refetch
queryClient.setQueryData(postKeys.detail(id), newData)
Suspense Mode
- Use
useSuspenseQueryfor Suspense-based data fetching (v5) - Wrap with
<Suspense fallback={<Skeleton />}> - Pair with
<ErrorBoundary>for error handling
// No need to handle isLoading — Suspense handles it
const { data } = useSuspenseQuery(postQueryOptions(postId))
Performance Best Practices
- Set appropriate
staleTimeper query — defaults to0(always stale) - Use
selectto transform/subscribe to only relevant slices of data - Use
placeholderData: keepPreviousDatafor pagination to avoid layout shifts - Avoid creating
QueryClientinside components — instantiate once at app root - Use
notifyOnChangePropsto limit re-renders to only relevant data changes
Error Handling
- Use
throwOnError: trueto bubble errors … (hier gekürzt — Kopieren/Download liefert die vollständige Regel)
## So nutzt du sie
Die Regel kopieren (Button oben) oder als Datei herunterladen und im Projekt unter `.cursor/rules/` ablegen — Cursor lädt sie beim nächsten Start automatisch. Ältere Cursor-Versionen lesen alternativ eine einzelne `.cursorrules`-Datei im Projektstamm; dort einfach den Regel-Text ohne den Kopfblock zwischen den `---`-Zeilen einfügen.
Der Regel-Text ist englisch — Cursor versteht ihn unabhängig von der Sprache, in der Sie mit dem Editor chatten.
## Im Detail
Wie tanstack-query, aber speziell auf Version 5 zugeschnitten: Die Regel berücksichtigt die Breaking Changes gegenüber älteren Versionen — etwa geänderte Signaturen bei useQuery (ein Objekt-Argument statt mehrerer Parameter) oder umbenannte Optionen. In .cursor/rules abgelegt, sorgt sie dafür, dass Cursor keinen veralteten v4-Code vorschlägt, was gerade bei gemischten Codebasen oder frisch migrierten Projekten häufig passiert. Sinnvoll, wenn dein Projekt bereits auf v5 läuft oder gerade migriert wird — für ältere Projekte ist die generische tanstack-query-Regel unter Umständen passender.
## Praxis-Tipp
Setze diese Regel statt der allgemeinen Tanstack-Query-Regel ein, sobald dein Projekt auf v5 migriert ist — so schlägt Cursor nicht versehentlich veraltete v4-Syntax vor.
## Lizenz & Quelle
- **Lizenz:** CC0 1.0
- **Quelle:** [PatrickJS/awesome-cursorrules (GitHub)](https://github.com/PatrickJS/awesome-cursorrules)
Inhalt ansehen (tanstack-query-v5.mdc)
Lade …
Erfahrungen & Kommentare.
Funktioniert der Regel bei Ihnen? Tipps, Stolperfallen, Varianten — teilen Sie es mit der Community.
Lade Kommentare …
Passt dazu.
AI Agent Specialist
Cursor-Regel, die den KI-Editor auf diszipliniertes, spezialisiertes Agenten-Verhalten trimmt.
Alpha Skills Quant Factor Research
Cursor-Regel für quantitative Faktor-Recherche im Trading/Finance-Bereich — leitet die KI zu methodisch sauberer Analyse an.
Android Jetpack Compose
Cursor-Regel für Android-Entwicklung mit Jetpack Compose — sorgt für idiomatischen, deklarativen Kotlin-UI-Code.
