Capítulo 18 de 24
This example shows the canonical pattern for combining TanStack Router with TanStack Query: route loaders call queryClient.ensureQueryData(queryOptions) to prefetch data before the route renders, and components then read that same cached data with useSuspenseQuery. The router's context carries the queryClient instance down to every loader.
createRoute/createRootRouteWithContext and assembled via addChildren)@tanstack/react-router, @tanstack/react-query, @tanstack/react-query-devtools, redaxiossrc/main.tsx defines the root route (typed with a queryClient context), the /posts layout route and $postId route, wires up createRouter with that context, and renders QueryClientProvider around RouterProvider. src/posts.ts holds the queryOptions() factories and fetch functions. src/posts.lazy.tsx is the code-split component for the posts layout, loaded via .lazy().// src/posts.ts
export const postQueryOptions = (postId: string) =>
queryOptions({
queryKey: ['posts', { postId }],
queryFn: () => fetchPost(postId),
})
export const postsQueryOptions = queryOptions({
queryKey: ['posts'],
queryFn: () => fetchPosts(),
})
// src/main.tsx
const postRoute = createRoute({
getParentRoute: () => postsLayoutRoute,
path: '$postId',
errorComponent: PostErrorComponent,
loader: ({ context: { queryClient }, params: { postId } }) =>
queryClient.ensureQueryData(postQueryOptions(postId)),
component: PostRouteComponent,
})
function PostRouteComponent() {
const { postId } = postRoute.useParams()
const postQuery = useSuspenseQuery(postQueryOptions(postId))
const post = postQuery.data
// ...
}
const router = createRouter({
routeTree,
defaultPreload: 'intent',
// Since we're using React Query, we don't want loader calls to ever be stale
defaultPreloadStaleTime: 0,
context: { queryClient },
})
queryOptions() definition between the route loader (prefetch via ensureQueryData) and the component (read via useSuspenseQuery), so there is a single source of truth for the query key and fetcher.defaultPreloadStaleTime: 0 on the router when React Query owns caching, otherwise the router's own preload staleness check can skip a loader call that React Query would otherwise consider stale.createRootRouteWithContext<{ queryClient: QueryClient }>() so every nested loader gets a typed context.queryClient without prop drilling.router.invalidate() from an error boundary (paired with useQueryErrorResetBoundary) to retry a failed loader/query together.queryOptions() factories that take params (like postQueryOptions(postId)) keep query keys colocated with their fetchers, avoiding key mismatches between loader and component.ensureQueryData + useQuery pattern but replaces raw queryOptions() calls with tRPC's trpc.<procedure>.queryOptions(), generated from a typed server router.