`setQueryData` does not work for queries with custom `queryKeyHashFn`
Describe the bug
If a query has a custom queryKeyHashFn, then trying to update its cached value with queryClient.setQueryData does not work.
It seems that whenever setQueryData is called, the default hash function is being used to identify the query no matter what, even if the query has specified its own hash function. This behavior is the same whether it's the default react query hash function, or if the user supplies their own global default hash function when initializing the query client.
The end result is that a new query is added to the cache, with its key being the result of the default hash function. But the original query that I intend to update is not updated.
Your minimal, reproducible example
https://codesandbox.io/s/xenodochial-jennings-mych19?file=/src/Components.tsx
Steps to reproduce
- Create new query via
useQueryand specify a customqueryKeyHashFn - Try to update the cached data with
queryClient.setQueryData - Notice that the cache is not updated
Expected behavior
I expect the custom hash function be used to identify the query if it has one
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- macOS monterey
- Chrome 104
react-query version
v4.2.1
TypeScript version
No response
Additional context
No response
yeah that's a good find!
setQueryData does take options as 3rd argument, but we would need to be able to pass:
queryKeyHashFnqueryHash
to accommodate this use-case.
I'm not sure if we should mangle the current options, which are of type SetDataOptions, to add these two fields 🤔
https://github.com/TanStack/query/blob/454c58481f6f5d5c3e182e6e418d65fdb4a9ec1f/packages/query-core/src/queryClient.ts#L132
Shouldn't we replace every usage of the hashQueryKey function with (this.defaultOptions.queries?.queryKeyHashFn ?? hashQueryKey) in QueryClient and other places that should respect the options? And then pass method level queryKeyHashFn?
yeah we have a function for this already:
https://github.com/TanStack/query/blob/f9ebf9aaae59050331c22f143d835e46fac402cc/packages/query-core/src/utils.ts#L257-L263
I guess we just need to accept relevant options and then use this everywhere?
I meant that I do not see where is queryClient.defaultOptions.queries?.queryKeyHashFn being used? Isn't the priority as follows: methodOptions?.queryKeyHashFn (to be done) > query?.queryKeyHashFn > queryClient.defaultOptions.queries?.queryKeyHashFn > hashQueryKey? I only see: query?.queryKeyHashFn > hashQueryKey
Example:
queryClient#getQueryData uses queryCache#find:
https://github.com/TanStack/query/blob/7db5da560e56647d019c0500e04dc1573c6e7f6b/packages/query-core/src/queryClient.ts#L109-L114
queryCache#find uses matchQuery:
https://github.com/TanStack/query/blob/7db5da560e56647d019c0500e04dc1573c6e7f6b/packages/query-core/src/queryCache.ts#L167-L178
matchQuery does not use defaultOptions.queries?.queryKeyHashFn
https://github.com/TanStack/query/blob/7db5da560e56647d019c0500e04dc1573c6e7f6b/packages/query-core/src/utils.ts#L172-L194
@aboqasem the query options are stored on the query itself, when the query is created. There, the options are merged with the default options:
https://github.com/TanStack/query/blob/7e51776bff00b2f6f8496a7a64b3a048238a4ee7/packages/query-core/src/queryCache.ts#L95-L108
after that, accessing query.options will read the "defaulted" options. Yes, it means that changing default values after a query is created does not influence those already created queries, but I think this is on purpose. Changing defaults also does not inform observers for that reason.
I looked into this a bit more today, and I think passing queryKeyHashFn to all functions that could potentially need it would be quite a chore for users as well.
I think if you need a custom queryKeyHashFn, the best thing you can do is:
- set it as a default, globally, for all queries
- set it as a default for your specific query key or range of keys:
queryClient.setQueryDefaults(myKey, { queryKeyHashFn })
the issue is that the queryKeyHashFn is a query-specific property, so it will be set once on the query, from the first thing that creates it. This could be via fetchQuery, or via setQueryData, or via useQuery, or via hydration, or ....
There are just too many possibilities, and you'd need to pass the function to all of them, and we'd need to handle it internally for all of them, too.
I think setQueryDefaults is the best solution for when you need that feature.
Thanks for checking. I ended up setting the global fn which ultimately works better I think.