T
TanStack3mo ago
sensitive-blue

Query data on-demand

In some cases I want to run a couple of queries in parallel on-demand (a button click for example). I have tested various ways to do this: - Use mutations with mutateAsync and Promise.all -> works but does not use caching - Set enabled: false on the query and use refetch() -> works but does not use caching - Add a flag for enabled and toggle it to start the queries -> works but hard to await the queries (like await Promise.all) - Use queryClient.ensureQueryData -> works but you have to manually handle loading states etc. I currently (option 4) have created functions in my services like this:
getFetchMixQueryPromise(mixId: string, staleTime?: number): Promise<MixLog> {
const fetchMixQueryOptions = this.getFetchMixQueryOptions(mixId, staleTime);

return this.queryClient.ensureQueryData<MixLog, Error>({
queryKey: fetchMixQueryOptions.queryKey,
queryFn: fetchMixQueryOptions.queryFn as QueryFunction<MixLog, readonly unknown[], never>,
staleTime: fetchMixQueryOptions.staleTime as StaleTime<MixLog, Error, MixLog, readonly unknown[]>,
revalidateIfStale: true,
});
}

getFetchMixQueryOptions(mixId: string, staleTime?: number): CreateQueryOptions<MixLog, Error, MixLog, string[]> {
return queryOptions({
queryKey: ["mixes", mixId],
queryFn: () => lastValueFrom(this.fetch(mixId)),
staleTime: staleTime ?? 0,
});
}
getFetchMixQueryPromise(mixId: string, staleTime?: number): Promise<MixLog> {
const fetchMixQueryOptions = this.getFetchMixQueryOptions(mixId, staleTime);

return this.queryClient.ensureQueryData<MixLog, Error>({
queryKey: fetchMixQueryOptions.queryKey,
queryFn: fetchMixQueryOptions.queryFn as QueryFunction<MixLog, readonly unknown[], never>,
staleTime: fetchMixQueryOptions.staleTime as StaleTime<MixLog, Error, MixLog, readonly unknown[]>,
revalidateIfStale: true,
});
}

getFetchMixQueryOptions(mixId: string, staleTime?: number): CreateQueryOptions<MixLog, Error, MixLog, string[]> {
return queryOptions({
queryKey: ["mixes", mixId],
queryFn: () => lastValueFrom(this.fetch(mixId)),
staleTime: staleTime ?? 0,
});
}
In my component I can then get a couple of promises and call await Promise.all to wait for all the queries to complete. Like I mentioned above, I still need to manually handle loading states etc. so I feel this is not a TanStack style approach. What would be the best way to trigger multiple queries in parallel and await them?
3 Replies
rare-sapphire
rare-sapphire3mo ago
Mutation are for changing data not querying. If you need to fetch data and await it you can use queryClient.fetchQuery(). If you need loading state and you want to use injectQuery() you can't await it. You can use enabled or skip token to disable query until the user does something that would trigger this query. If you need to do something with the data fetched from the query. You can either use select on injectQuery, angular's computed if you need to combine multiple queries, or angular's effect if you need to do something after the data has fetched.
sensitive-blue
sensitive-blueOP3mo ago
Thanks Sergey! I've refactored my code. Now I have a boolean signal to trigger my queries. Then I have a computed signal that's a combination of the boolean and the data of each query. And I have an effect that checks the data of the computed signal to see if the data from all the queries is available and then performs the required action. Something like:
queryOne = injectQuery(() => ({
...this.myService.getQueryOneOptions(),
enabled: this.useQueriesData(),
}));

queryTwo = injectQuery(() => ({
...this.myService.getQueryTwoOptions(),
enabled: this.useQueriesData(),
}));

queryThree = injectQuery(() => ({
...this.myService.getQueryThreeOptions(),
enabled: this.useQueriesData(),
}));

queriesData = computed(() => {
return {
_: this.useQueriesData(),
queryOneData: this.queryOne.data(),
queryTwoData: this.queryTwo.data(),
queryThreeData: this.queryThree.data(),
};
});
queryOne = injectQuery(() => ({
...this.myService.getQueryOneOptions(),
enabled: this.useQueriesData(),
}));

queryTwo = injectQuery(() => ({
...this.myService.getQueryTwoOptions(),
enabled: this.useQueriesData(),
}));

queryThree = injectQuery(() => ({
...this.myService.getQueryThreeOptions(),
enabled: this.useQueriesData(),
}));

queriesData = computed(() => {
return {
_: this.useQueriesData(),
queryOneData: this.queryOne.data(),
queryTwoData: this.queryTwo.data(),
queryThreeData: this.queryThree.data(),
};
});
and
effect(() => {
const { queryOneData, queryTwoData, queryThreeData} = this.queriesData();

if (!this.useQueriesData() || !queryOneData || !queryTwoData || !queryThreeData) {
return;
}

// Perform side-effect with akll three query data here
});
effect(() => {
const { queryOneData, queryTwoData, queryThreeData} = this.queriesData();

if (!this.useQueriesData() || !queryOneData || !queryTwoData || !queryThreeData) {
return;
}

// Perform side-effect with akll three query data here
});
Do you see any issues with this approach?
rare-sapphire
rare-sapphire3mo ago
Yeah it's fine I believe, just be aware that queries aren't synchronized between each other. So when you refetch data you might end up with new data from query one and old data from another query for a brief moment

Did you find this page helpful?