IDE-Tools · Foto: Homedust, CC BY 2.0
Tanstack Query V5
Zuletzt aktualisiert:
Typ
Regel
Lizenz
CC0 1.0
Anwendungsfeld
Cursor Rules
Voraussetzungen
Keine besonderen — direkt loslegen.
Cursor-Regel 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
Speziell auf TanStack Query Version 5 zugeschnitten, berücksichtigt diese Regel die wichtigsten Breaking Changes gegenüber v4: die neue Objekt-Signatur für useQuery statt mehrerer Parameter, umbenannte Optionen wie isPending statt isLoading sowie Änderungen an Suspense-Hooks. Das ist hilfreich, weil die KI sonst mit veraltetem v4-Wissen aus dem Training arbeiten und falsche Syntax vorschlagen würde. Für Projekte, die frisch auf v5 migriert sind oder neu damit starten, verhindert die Regel, dass generierter Code nicht kompiliert oder auf deprecated APIs zurückgreift. Wer noch v4 nutzt, sollte stattdessen die allgemeine TanStack-Query-Regel verwenden.
## Praxis-Tipp
Prüfen Sie nach jedem KI-Vorschlag kurz, ob isPending statt isLoading verwendet wird – das ist der häufigste Stolperstein beim v4-zu-v5-Wechsel.
## 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 für diszipliniertes, klar abgegrenztes Agenten-Verhalten statt unstrukturierter KI-Antworten.
Alpha Skills Quant Factor Research
Cursor-Regel für methodisch saubere quantitative Faktor-Recherche im Trading-Umfeld.
Android Jetpack Compose
Cursor-Regel für idiomatischen, deklarativen Android-UI-Code mit Jetpack Compose.
