# load-ensure-query-data: Use ensureQueryData with TanStack Query
## Priority: HIGH
## Explanation
When integrating TanStack Router with TanStack Query, use `queryClient.ensureQueryData()` in loaders instead of `prefetchQuery()`. This respects the cache, awaits data if missing, and returns the data for potential use.
## Bad Example
```tsx
// Using prefetchQuery - doesn't return data, can't await stale check
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params, context: { queryClient } }) => {
// prefetchQuery never throws, swallows errors
queryClient.prefetchQuery({
queryKey: ['posts', params.postId],
queryFn: () => fetchPost(params.postId),
})
// No await - might not complete before render
// No return value to use
},
})
// Fetching directly - bypasses TanStack Query cache
export const Route = createFileRoute('/posts')({
loader: async () => {
const posts = await fetchPosts() // Not cached
return { posts }
},
})
```
## Good Example
```tsx
// Define queryOptions for reuse
const postQueryOptions = (postId: string) =>
queryOptions({
queryKey: ['posts', postId],
queryFn: () => fetchPost(postId),
staleTime: 5 * 60 * 1000, // 5 minutes
})
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params, context: { queryClient } }) => {
// ensureQueryData:
// - Returns cached data if fresh
// - Fetches and caches if missing or stale
// - Awaits completion
// - Throws on error (caught by error boundary)
await queryClient.ensureQueryData(postQueryOptions(params.postId))
},
component: PostPage,
})
function PostPage() {
const { postId } = Route.useParams()
// Data guaranteed to exist from loader
const { data: post } = useSuspenseQuery(postQueryOptions(postId))
return
}
```
## Good Example: Multiple Parallel Queries
```tsx
export const Route = createFileRoute('/dashboard')({
loader: async ({ context: { queryClient } }) => {
// Parallel data fetching
await Promise.all([
queryClient.ensureQueryData(statsQueries.overview()),
queryClient.ensureQueryData(activityQueries.recent()),
queryClient.ensureQueryData(notificationQueries.unread()),
])
},
})
```
## Good Example: Dependent Queries
```tsx
export const Route = createFileRoute('/users/$userId/posts')({
loader: async ({ params, context: { queryClient } }) => {
// First query needed for second
const user = await queryClient.ensureQueryData(
userQueries.detail(params.userId)
)
// Dependent query uses result
await queryClient.ensureQueryData(
postQueries.byAuthor(user.id)
)
},
})
```
## Router Configuration for TanStack Query
```tsx
// router.tsx
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute default
},
},
})
export const router = createRouter({
routeTree,
context: { queryClient },
// Let TanStack Query manage caching
defaultPreloadStaleTime: 0,
// SSR: Dehydrate query cache
dehydrate: () => ({
queryClientState: dehydrate(queryClient),
}),
// SSR: Hydrate on client
hydrate: (dehydrated) => {
hydrate(queryClient, dehydrated.queryClientState)
},
// Wrap with QueryClientProvider
Wrap: ({ children }) => (
{children}
),
})
```
## ensureQueryData vs prefetchQuery vs fetchQuery
| Method | Returns | Throws | Awaits | Use Case |
|--------|---------|--------|--------|----------|
| `ensureQueryData` | Data | Yes | Yes | Route loaders (recommended) |
| `prefetchQuery` | void | No | Yes | Background prefetching |
| `fetchQuery` | Data | Yes | Yes | When you need data immediately |
## Context
- `ensureQueryData` is the recommended method for route loaders
- Respects `staleTime` - won't refetch fresh cached data
- Errors propagate to route error boundaries
- Use `queryOptions()` factory for type-safe, reusable query definitions
- Set `defaultPreloadStaleTime: 0` to let TanStack Query manage cache
- Pair with `useSuspenseQuery` in components for guaranteed data