T
TanStack8mo ago
fascinating-indigo

Returning query options in loaderDeps to prevent duplication

When using tanstack query with tanstack router all the examples duplicate the query options between the ensureQueryData and useSuspenseQuery as below.
const postsQueryOptions = queryOptions({
queryKey: ['posts'],
queryFn: () => fetchPosts(),
})

export const Route = createFileRoute('/posts')({
// Use the `loader` option to ensure that the data is loaded
loader: () => queryClient.ensureQueryData(postsQueryOptions),
component: () => {
// Read the data from the cache and subscribe to updates
const {
data: { posts },
} = useSuspenseQuery(postsQueryOptions)

return (
<div>
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</div>
)
},
})
const postsQueryOptions = queryOptions({
queryKey: ['posts'],
queryFn: () => fetchPosts(),
})

export const Route = createFileRoute('/posts')({
// Use the `loader` option to ensure that the data is loaded
loader: () => queryClient.ensureQueryData(postsQueryOptions),
component: () => {
// Read the data from the cache and subscribe to updates
const {
data: { posts },
} = useSuspenseQuery(postsQueryOptions)

return (
<div>
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</div>
)
},
})
Is there any issues with returning the query options in loaderDeps? This is particularly useful when we need to use search parameters in our query options like below
export const Route = createFileRoute("/")({
component: RouteComponent,
validateSearch: v.object({
page: v.optional(v.fallback(v.number(), defaultValues.page), defaultValues.page),
limit: v.optional(v.fallback(v.number(), defaultValues.limit), defaultValues.limit),
}),
loaderDeps: ({ search }) => articlesQueryOptions({ limit: search.limit, offset: search.page - 1 }),
loader: ({ context: { queryClient }, deps }) => queryClient.ensureQueryData(deps),
search: { middlewares: [stripSearchParams(defaultValues)] },
});

function RouteComponent() {
const articlesQuery = useSuspenseQuery(Route.useLoaderDeps());
}
export const Route = createFileRoute("/")({
component: RouteComponent,
validateSearch: v.object({
page: v.optional(v.fallback(v.number(), defaultValues.page), defaultValues.page),
limit: v.optional(v.fallback(v.number(), defaultValues.limit), defaultValues.limit),
}),
loaderDeps: ({ search }) => articlesQueryOptions({ limit: search.limit, offset: search.page - 1 }),
loader: ({ context: { queryClient }, deps }) => queryClient.ensureQueryData(deps),
search: { middlewares: [stripSearchParams(defaultValues)] },
});

function RouteComponent() {
const articlesQuery = useSuspenseQuery(Route.useLoaderDeps());
}
1 Reply
fair-rose
fair-rose8mo ago
i would recommend using the context function instead
export const Route = createFileRoute("/")({
component: RouteComponent,
validateSearch: v.object({
page: v.optional(v.fallback(v.number(), defaultValues.page), defaultValues.page),
limit: v.optional(v.fallback(v.number(), defaultValues.limit), defaultValues.limit),
}),
loaderDeps: ({ search }) => ({limit: search.limit, page: search.page}),
context: ({ deps }) => ({queryOptions: articlesQueryOptions({ limit: deps.limit, offset: deps.page - 1 })}),
loader: ({ context: { queryClient, queryOptions }, deps }) => queryClient.ensureQueryData(queryOptions),
search: { middlewares: [stripSearchParams(defaultValues)] },
});

function RouteComponent() {
const articlesQuery = useSuspenseQuery(Route.useRouteContext({select: ({queryOptions}) => queryOptions});
}
export const Route = createFileRoute("/")({
component: RouteComponent,
validateSearch: v.object({
page: v.optional(v.fallback(v.number(), defaultValues.page), defaultValues.page),
limit: v.optional(v.fallback(v.number(), defaultValues.limit), defaultValues.limit),
}),
loaderDeps: ({ search }) => ({limit: search.limit, page: search.page}),
context: ({ deps }) => ({queryOptions: articlesQueryOptions({ limit: deps.limit, offset: deps.page - 1 })}),
loader: ({ context: { queryClient, queryOptions }, deps }) => queryClient.ensureQueryData(queryOptions),
search: { middlewares: [stripSearchParams(defaultValues)] },
});

function RouteComponent() {
const articlesQuery = useSuspenseQuery(Route.useRouteContext({select: ({queryOptions}) => queryOptions});
}
context is executed the same way as loader , so if you supply a queryFn in the query options and that queryFn e.g. binds a value, you are only creating that function once. so inside of your component, you do not re-render since the queryFn and queryOptions are referentially stable

Did you find this page helpful?