.refetch() is not working as expected after signal update when using injectQuery
Describe the bug
It seems like injectInfiniteQuery that depends on some signal-based param, like query() for the queryKey/queryFn, is not working as expected when we use the .refetch() directly after updating the query signal, something like:
Edit: seems like relevant for injectQuery as well
query = signal('');
postsQuery = injectInfiniteQuery(() => ({
queryKey: ['posts', this.query()],
queryFn: ({ pageParam = 0 }) =>
firstValueFrom(this.postsService.getPosts(pageParam, this.query())),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextId,
getPreviousPageParam: (firstPage) => firstPage.previousId,
enabled: false,
placeholderData: keepPreviousData,
}));
then
search() {
this.query.set('foo')
this.postsQuery.refetch()
}
This isn't working (view stackblitz)
The solution is to delay the refetch call:
search() {
this.query.set('foo')
setTimeout(() => this.postsQuery.refetch())
}
Your minimal, reproducible example
https://stackblitz.com/edit/sb1-6pedarrr?file=src%2Fapp%2Fcomponents%2Fposts-list%2Fposts-list.component.ts
Steps to reproduce
-
type
sitin the search input -
press
Searchbutton -
this will trigger a
.refetch()with the updated search term and show the results as expected -
remove the
setTimeout()in the end of the file, and callrefetch()directly
this.postsQuery.refetch();
//setTimeout(() => this.postsQuery.refetch());
- this is no longer works as expected
Expected behavior
refetch() should work as expected without setTimeout()
How often does this bug happen?
None
Screenshots or Videos
No response
Platform
- OS: macOS 15.5
- Browser: latest chrome
Tanstack Query adapter
angular-query
TanStack Query version
v5.76.0
TypeScript version
v5.8.2
Additional context
in my environment with v5.51.15, the HTTP call is made with the wrong param (the previous query() value)
and I tried to repro it with stackblitz, where it behaves differently (probably because of different versions) - but still not as expected.
That seems to be a behavior that is not specific to injectInfiniteQuery but would also be present for injectQuery.
This likely a consequence of using effect in the definition of the injection function as effect unlike computed will only run eventually after the dependencies have been updated. As a fix, one could, therefore, consider removing the effects as proposed for injectMutation in https://github.com/TanStack/query/pull/9098.
Hi :)
Any work regarding this issue?
Its getting messy really quick once we go setTimeout the template has many potential glitches and invalid states caused by the internal use of effect
I think we found a workaround, fetching with the queryClient instead of directly use .refetch()
this.queryClient.fetchInfiniteQuery(options);
The problem might be not the query function itself but rather the object you return from builder. This approach works like wonder for me:
private readonly postsQuery = injectInfiniteQuery(() => {
const postsFilter = this.query();
return {
queryKey: ['posts', postsFilter],
queryFn: ({ pageParam = 0 }) =>
firstValueFrom(this.postsService.getPosts(pageParam, postsFilter)),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextId,
getPreviousPageParam: (firstPage) => firstPage.previousId,
enabled: false,
placeholderData: keepPreviousData,
};
});
@bebrasmell I don't see how this actually matters (reading the signal before or inside the return.
you can see here it does not fix the problem https://stackblitz.com/edit/sb1-t6c4vtyt?file=src%2Fapp%2Fcomponents%2Fposts-list%2Fposts-list.component.ts
its a timing issue
looking at the initial reproduction:
search() {
this.query.set('foo')
this.postsQuery.refetch()
}
since query is (correctly) part of the key:
queryKey: ['posts', this.query()],
there is no need to manually call refetch. Changing a value that is part of the key will result in the QueryObserver starting to observe a different Query, and if there’s no data or stale data for that Query, you’ll get an automatic refetch.
The react equivalent would be:
const [query, setQuery] = useState('')
postsQuery = useInfiniteQuery({
queryKey: ['posts', query],
queryFn: ({ pageParam = 0 }) =>
firstValueFrom(this.postsService.getPosts(pageParam, this.query())),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextId,
getPreviousPageParam: (firstPage) => firstPage.previousId,
enabled: false,
placeholderData: keepPreviousData,
});
then:
search() {
setQuery('foo')
there is no need to manually call refetch
But the query is enabled: false
So changing the key isn’t enough, therefore the refetch() call.
You can try the stackblitz Example and look on the network calls
but why is the query enabled: false ? If you want to disable the query while it’s empty, you can do:
enabled: query !== ''
This is documented under lazy queries:
https://tanstack.com/query/latest/docs/framework/angular/guides/disabling-queries#lazy-queries
As it stands, refetch will always execute the queryFn it has currently stored, which means it will close over all values and have access to the queryKey from creation time. This is expected.
If calling setQuery('foo') doesn’t synchronously re-run the component and thus update the Query, this is expected.
React works the same way: A call to setState merely schedules an update, so calling refetch() right after that won’t see the latest value, except if you call flushSync() in between.
Mostly, you don’t want enabled: false. We also document this:
Permanently disabling a query opts out of many great features that TanStack Query has to offer (like background refetches), and it's also not the idiomatic way. It takes you from the declarative approach (defining dependencies when your query should run) into an imperative mode (fetch whenever I click here). It is also not possible to pass parameters to refetch. Oftentimes, all you want is a lazy query that defers the initial fetch