PluginBench
Rule

react tanstack router query

via PatrickJS/awesome-cursorrules

Zero-loading-spinner React SPA with TanStack Router v1 + TanStack Query v5 for type-safe URLs and cache-first data.

What is react tanstack router query?

Establishes the definitive pattern for combining TanStack Router (routing & URL state) with TanStack Query (server state & caching) in React SPAs. Loaders prefetch data into the Query cache before render, eliminating loading spinners. Use this rule when building data-driven single-page applications that prioritize instant navigation and type-safe routing.

  • Prefetch route data via loaders into Query cache before component render for zero loading spinners
  • Define reusable queryOptions outside components to ensure type safety and cache consistency
  • Map search params to Query keys for filter and pagination state management
  • Warm the cache after mutations and invalidate stale queries for instant UI feedback
  • Enable hover prefetching on links to preload data on user intent

Applies to

File patterns this rule matches.

["src/routes/**/*"
src/queries/**/*
src/lib/router.ts
"src/lib/queryClient.ts"]
Rule definition (reference)

Source of truth, from the repository.

You are an expert in React, TanStack Router v1, TanStack Query v5, TypeScript, and Vite.

Architecture

  • TanStack Router: routing, URL state, navigation
  • TanStack Query: server state, caching, mutations
  • Loader = bridge: prefetches into Query cache before render → zero loading spinners for route data
  • Components are pure UI: read from Query cache, trigger mutations

Setup

// src/lib/queryClient.ts
export const queryClient = new QueryClient({
  defaultOptions: { queries: { staleTime: 60_000 } },
})

// src/lib/router.ts
export const router = createRouter({
  routeTree,
  context: { queryClient },
  defaultPreload: 'intent',
  defaultPreloadStaleTime: 0,
})

declare module '@tanstack/react-router' {
  interface Register { router: typeof router }
}

// src/main.tsx
<QueryClientProvider client={queryClient}>
  <RouterProvider router={router} context={{ queryClient }} />
</QueryClientProvider>

Query Definitions

// src/queries/posts.ts
export const postKeys = {
  all: ['posts'] as const,
  detail: (id: string) => [...postKeys.all, 'detail', id] as const,
  list: (f?: PostFilters) => [...postKeys.all, 'list', f] as const,
}

export const postQueryOptions = (id: string) =>
  queryOptions({ queryKey: postKeys.detail(id), queryFn: () => fetchPost(id) })

export const postsQueryOptions = (filters?: PostFilters) =>
  queryOptions({ queryKey: postKeys.list(filters), queryFn: () => fetchPosts(filters) })

Loader + Component (zero loading state)

export const Route = createFileRoute('/posts/$postId')({
  loader: ({ context: { queryClient }, params }) =>
    queryClient.ensureQueryData(postQueryOptions(params.postId)),
  component: PostDetail,
})

function PostDetail() {
  const { postId } = Route.useParams()
  const { data: post } = useQuery(postQueryOptions(postId))  // always in cache from loader
  return <h1>{post!.title}</h1>
}

Search Params → Query Key

const searchSchema = z.object({ page: z.number().default(1), q: z.string().optional() })

export const Route = createFileRoute('/posts/')({
  validateSearch: searchSchema,
  loader: ({ context: { queryClient }, location: { search } }) =>
    queryClient.ensureQueryData(postsQueryOptions(search)),
  component: PostsList,
})

function PostsList() {
  const search = Route.useSearch()
  const { data } = useQuery(postsQueryOptions(search))
  // ...
}

Mutations

const mutation = useMutation({
  mutationFn: createPost,
  onSuccess: (newPost) => {
    queryClient.setQueryData(postKeys.detail(newPost.id), newPost)  // warm cache
    queryClient.invalidateQueries({ queryKey: postKeys.list() })
    navigate({ to: '/posts/$postId', params: { postId: newPost.id } })  // instant — no spinner
  },
})

Hover Prefetching

<Link
  to="/posts/$postId"
  params={{ postId: post.id }}
  onMouseEnter={() => queryClient.prefetchQuery(postQueryOptions(post.id))}
>
  {post.title}
</Link>

Key Rules

  • Always define queryOptions outside components — never inline inside useQuery()
  • Never use useEffect for data fetching — use loaders or useQuery
  • Search params are the single source of truth for filter/pagination state
  • After mutations: setQueryData + invalidateQueries for instant UI feedback
  • declare module '@tanstack/react-router' router registration is required for full type safety

Related rules

Zero-loading-spinner routing with TanStack Router v1 and Query v5 for type-safe React SPAs

**/*
41k
via PatrickJS/awesome-cursorrules

Expert guidance for React, TypeScript, Next.js 14, and Web3 stack development with Wagmi and Viem.

**/*
41k
via PatrickJS/awesome-cursorrules

Full-stack React, TypeScript, and Symfony development assistant with Docker expertise.

**/*
41k
via PatrickJS/awesome-cursorrules

React and TypeScript state management guidance for Zustand stores, selectors, middleware, persistence, and testing.

**/*.ts +1
41k
via PatrickJS/awesome-cursorrules

Write READMEs that convert readers in 3-5 seconds with landing-page structure and working code examples.

**/*
41k
via PatrickJS/awesome-cursorrules

ROS and ROS2 best practices for packages, nodes, interfaces, timing, and testing.

["**/*.py" +11
41k
via PatrickJS/awesome-cursorrules