-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
feat: added TanStack Query v5 cursorrules #247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -181,6 +181,7 @@ By creating a `.cursorrules` file in your project's root directory, you can leve | |||||
| - [React (Redux, TypeScript)](./rules/react-redux-typescript-cursorrules-prompt-file/.cursorrules) - Cursor rules for React development with Redux and TypeScript integration. | ||||||
| - [React (MobX)](./rules/react-mobx-cursorrules-prompt-file/.cursorrules) - Cursor rules for React development with MobX integration. | ||||||
| - [React (React Query)](./rules/react-query-cursorrules-prompt-file/.cursorrules) - Cursor rules for React development with React Query integration. | ||||||
| - [TanStack Query v5](/rules/tanstack-query-v5-cursorrules-prompt-file/.cursorrules) - Cursor rules for TanStack Query v5 including queryOptions helper, query key factories, mutations, optimistic updates, infinite queries, and Suspense mode. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the link path to use relative format. The link should start with 🔗 Proposed fix-- [TanStack Query v5](/rules/tanstack-query-v5-cursorrules-prompt-file/.cursorrules) - Cursor rules for TanStack Query v5 including queryOptions helper, query key factories, mutations, optimistic updates, infinite queries, and Suspense mode.
+- [TanStack Query v5](./rules/tanstack-query-v5-cursorrules-prompt-file/.cursorrules) - Cursor rules for TanStack Query v5 including queryOptions helper, query key factories, mutations, optimistic updates, infinite queries, and Suspense mode.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| ### Database and API | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,108 @@ | ||||||||||||
| --- | ||||||||||||
| description: TanStack Query v5 (React Query) patterns including queryOptions helper, query key factories, mutations, optimistic updates, infinite queries, Suspense mode, and prefetching | ||||||||||||
| globs: ["src/**/*.tsx", "src/**/*.ts", "src/queries/**/*"] | ||||||||||||
| alwaysApply: false | ||||||||||||
| --- | ||||||||||||
|
|
||||||||||||
| 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: `useQuery` only accepts options object form — no positional args | ||||||||||||
|
|
||||||||||||
| ## QueryClient Setup | ||||||||||||
| ```tsx | ||||||||||||
| const queryClient = new QueryClient({ | ||||||||||||
| defaultOptions: { | ||||||||||||
| queries: { | ||||||||||||
| staleTime: 1000 * 60, | ||||||||||||
| retry: (count, error: any) => error?.status !== 404 && count < 2, | ||||||||||||
|
||||||||||||
| retry: (count, error: any) => error?.status !== 404 && count < 2, | |
| retry: (count, error: { status?: number }) => error.status !== 404 && count < 2, |
Copilot
AI
Apr 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
postKeys.list appends filters even when it’s undefined, producing query keys like ['posts','list', undefined]. This makes the “no filters” key shape a bit awkward and can lead to accidental cache misses if some call sites omit the element. Consider conditionally omitting the filters segment when it’s not provided, or make filters required.
| list: (filters?: PostFilters) => [...postKeys.lists(), filters] as const, | |
| list: (filters?: PostFilters) => | |
| filters === undefined | |
| ? postKeys.lists() | |
| : [...postKeys.lists(), filters] as const, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if rules-new directory is used elsewhere in the repository
fd -t d "rules-new"
# Check if there are other .mdc files and their locations
fd -e mdc
# Compare the two files to confirm they're identical
echo "=== Comparing the two tanstack-query.mdc files ==="
diff rules-new/tanstack-query.mdc rules/tanstack-query-v5-cursorrules-prompt-file/tanstack-query.mdcRepository: PatrickJS/awesome-cursorrules
Length of output: 50385
🏁 Script executed:
#!/bin/bash
# Verify file existence and size
echo "=== File existence and sizes ==="
wc -l rules-new/tanstack-query.mdc
wc -l rules/tanstack-query-v5-cursorrules-prompt-file/tanstack-query.mdc
# Use checksum to verify if identical
echo -e "\n=== File checksums ==="
md5sum rules-new/tanstack-query.mdc
md5sum rules/tanstack-query-v5-cursorrules-prompt-file/tanstack-query.mdc
# Check for any other references to rules-new in config files
echo -e "\n=== References to rules-new in configuration files ==="
find . -maxdepth 2 -type f \( -name "*.json" -o -name "*.yaml" -o -name "*.yml" -o -name "*.md" -o -name ".cursorrules" \) -exec grep -l "rules-new" {} \; 2>/dev/null || echo "No config references found"Repository: PatrickJS/awesome-cursorrules
Length of output: 453
Remove the duplicate file rules-new/tanstack-query.mdc.
This file is identical to rules/tanstack-query-v5-cursorrules-prompt-file/tanstack-query.mdc (confirmed by matching file size and checksum) and violates the repository's standard directory structure. The rules-new/ directory doesn't follow the established rules/<feature-name>/ pattern and has no references in configuration files or documentation, suggesting it was mistakenly committed. Delete this duplicate.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@rules-new/tanstack-query.mdc` around lines 1 - 108, Delete the duplicate file
rules-new/tanstack-query.mdc because it is identical to
rules/tanstack-query-v5-cursorrules-prompt-file/tanstack-query.mdc; remove the
file from the commit/branch (git rm rules-new/tanstack-query.mdc) and update the
PR so only the canonical file under rules/... remains, then run a quick
grep/search to confirm no other code or config references
rules-new/tanstack-query.mdc before finalizing the commit.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| 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 | ||
| ```ts | ||
| // 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 | ||
| ```ts | ||
| 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 | ||
| ```tsx | ||
| 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 | ||
| ```tsx | ||
| 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 | ||
| ```tsx | ||
| 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 | ||
| ```tsx | ||
| 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 | ||
| ```ts | ||
| // 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 | ||
| ```ts | ||
| // 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 `useSuspenseQuery` for Suspense-based data fetching (v5) | ||
| - Wrap with `<Suspense fallback={<Skeleton />}>` | ||
| - Pair with `<ErrorBoundary>` for error handling | ||
| ```tsx | ||
| // No need to handle isLoading — Suspense handles it | ||
| const { data } = useSuspenseQuery(postQueryOptions(postId)) | ||
| ``` | ||
|
|
||
| ## Performance Best Practices | ||
| - Set appropriate `staleTime` per query — defaults to `0` (always stale) | ||
| - Use `select` to transform/subscribe to only relevant slices of data | ||
| - Use `placeholderData: keepPreviousData` for pagination to avoid layout shifts | ||
| - Avoid creating `QueryClient` inside components — instantiate once at app root | ||
| - Use `notifyOnChangeProps` to limit re-renders to only relevant data changes | ||
|
|
||
| ## Error Handling | ||
| - Use `throwOnError: true` to bubble errors to the nearest ErrorBoundary | ||
| - Use `retry` function for conditional retry logic (e.g., skip retry on 404) | ||
| ```ts | ||
| retry: (failureCount, error) => { | ||
| if (error.status === 404) return false | ||
| return failureCount < 3 | ||
| }, | ||
| ``` | ||
|
|
||
| ## TypeScript Tips | ||
| - Always type `queryFn` return value explicitly or infer from typed API functions | ||
| - Use `QueryObserverResult<TData, TError>` to type hook return values | ||
| - Use `UseMutationResult<TData, TError, TVariables>` for mutations |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # TanStack Query v5 Cursor Rules | ||
|
|
||
| Cursor rules for TanStack Query v5 (React Query), covering the v5 API changes, `queryOptions` helper, query key factories, mutations, optimistic updates, infinite queries, Suspense mode, prefetching, and cache management. | ||
|
|
||
| ## What's covered | ||
| - QueryClient setup with sensible defaults | ||
| - Query key factory pattern | ||
| - `queryOptions()` helper for reusable query definitions | ||
| - `useQuery`, `useMutation`, `useInfiniteQuery` patterns | ||
| - Optimistic updates with rollback | ||
| - Suspense mode with `useSuspenseQuery` | ||
| - Prefetching and TanStack Router loader integration | ||
| - Cache invalidation strategies | ||
| - TypeScript best practices | ||
|
|
||
| ## Author | ||
| Created by [usm4nhafeez](https://github.com/usm4nhafeez) | ||
|
|
||
| Contributed to [awesome-cursorrules](https://github.com/PatrickJS/awesome-cursorrules) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new README entry uses an absolute link (
/rules/...) while surrounding entries use relative links (./rules/...). Absolute links can break when viewing the file in different contexts (e.g., GitHub vs rendered docs). Consider changing this to a relative link consistent with the rest of the list.