Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link

Copilot AI Apr 13, 2026

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.

Suggested change
- [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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the link path to use relative format.

The link should start with ./ instead of / to maintain consistency with all other links in this README. As per coding guidelines, all links in the README.md file should be relative and correct.

🔗 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- [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.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 184, Update the README link for "TanStack Query v5" so it
uses a relative path (prefix with ./) instead of an absolute path; locate the
link text "TanStack Query v5" and change the href from
"/rules/tanstack-query-v5-cursorrules-prompt-file/.cursorrules" to
"./rules/tanstack-query-v5-cursorrules-prompt-file/.cursorrules" to match the
other README links.


### Database and API

Expand Down
108 changes: 108 additions & 0 deletions rules-new/tanstack-query.mdc
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,
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retry callback types error as any, which conflicts with the “TypeScript best practices” goal of this ruleset and makes it easy to access non-existent properties. Prefer unknown (with narrowing) or a small structural type for the fields you use (e.g., status?: number).

Suggested change
retry: (count, error: any) => error?.status !== 404 && count < 2,
retry: (count, error: { status?: number }) => error.status !== 404 && count < 2,

Copilot uses AI. Check for mistakes.
},
},
})
```

## Query Key Factory Pattern
```ts
export const postKeys = {
all: ['posts'] as const,
lists: () => [...postKeys.all, 'list'] as const,
list: (filters?: PostFilters) => [...postKeys.lists(), filters] as const,
Copy link

Copilot AI Apr 13, 2026

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.

Suggested change
list: (filters?: PostFilters) => [...postKeys.lists(), filters] as const,
list: (filters?: PostFilters) =>
filters === undefined
? postKeys.lists()
: [...postKeys.lists(), filters] as const,

Copilot uses AI. Check for mistakes.
details: () => [...postKeys.all, 'detail'] as const,
detail: (id: string) => [...postKeys.details(), id] as const,
}
```

## queryOptions Helper (v5)
```ts
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
```tsx
const { mutate, isPending } = useMutation({
mutationFn: (input: CreatePostInput) => createPost(input),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: postKeys.lists() })
},
onError: (error) => toast.error(error.message),
})
```

## Optimistic Updates
```tsx
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
```tsx
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)
```tsx
// useSuspenseQuery — no isLoading needed, Suspense handles it
const { data } = useSuspenseQuery(postQueryOptions(postId))
// Wrap with <Suspense fallback={<Skeleton />}> + <ErrorBoundary>
```

## Key Rules
- Always define `queryOptions` outside components — never inline in `useQuery()`
- Never use `useEffect` to fetch data — use loaders or `useQuery`
- Use `placeholderData: keepPreviousData` for pagination to avoid layout shifts
- Instantiate `QueryClient` once at app root — never inside a component
Comment on lines +1 to +108
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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.mdc

Repository: 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.

204 changes: 204 additions & 0 deletions rules/tanstack-query-v5-cursorrules-prompt-file/.cursorrules
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
19 changes: 19 additions & 0 deletions rules/tanstack-query-v5-cursorrules-prompt-file/README.md
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)
Loading
Loading