-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
Describe the bug
if queryFn uses some external variables, it's behavior is not the same when called using query.refetch
and using queryClient.invalidateQueries
:
query.refetch
always uses latest version of queryFnqueryClient.invalidateQueries
always uses some outdated version of queryFn: either the one that was first provided touseQuery
or queryFn that was used in lastquery.refetch
.
Your minimal, reproducible example
https://codesandbox.io/p/sandbox/interesting-banzai-r3rfts
Steps to reproduce
- Click on
Change external param
button - it will generate new value ofexternalParam
variable ('1') - Click on
Invalidate
button - pay attention that param value in alert modal ('0') is not the same as current value ofexternalParam
variable ('1') - Click on
Refetch
button - now externalParam in alert is up to date ('1') - Click on
Invalidate
button - new externalParam in alert is also up to date ('1')
Expected behavior
Behavior of queryFn should be consistent. So either:
- Only first version of queryFn that was passed to
useQuery
should be used in bothquery.refetch
andqueryClient.invalidateQueries
(so, it's up to the end user to care about using references to lateset versions of external variables inside queryFn)
OR - both
query.refetch
andqueryClient.invalidateQueries
should use latest version of queryFn.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- OS: any
- Browser: any
- Version: any
Tanstack Query adapter
react-query
TanStack Query version
4.33.0
TypeScript version
No response
Additional context
In real life app this bug affects application a lot. In every queryFn in the app we use auth token approximately like this:
const token = useAuthToken();
useQuery({
queryKey: [...],
queryFn: async () => {
return await fetch('/api/...', {
headers: {
'Authorization': `Bearer ${token}`
}
});
}
});
So, token is retrieved from external hook, and then used inside queryFn.
Token expires after one hour. To prolong session we fetch new token in the background. So, every hour this external token
variable changes. But if later we use queryClient.invalidateQueries()
to invalidate this query - some old token is used to refetch the data because of this bug. And obviously that always leads to "token expired" error from backend.
For now we could solve that by storing reference to queryFn (using custom useStaticCallback
helper):
const useStaticCallback = (fn) => {
const ref = useRef(fn)
ref.current = fn
return useCallback(
(...args) => ref.current.apply(undefined, args),
[]
)
}
const token = useAuthToken();
useQuery({
queryKey: [...],
queryFn: useStaticCallback(async () => {
return await fetch('/api/...', {
headers: {
'Authorization': `Bearer ${token}`
}
});
})
});
UPD.: workaround above wouldn't work as expected in all possible cases. This proposal is the only proper way to go.