query icon indicating copy to clipboard operation
query copied to clipboard

`setQueryData` does not work for queries with custom `queryKeyHashFn`

Open tyko0x opened this issue 3 years ago • 5 comments

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

  1. Create new query via useQuery and specify a custom queryKeyHashFn
  2. Try to update the cached data with queryClient.setQueryData
  3. 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

tyko0x avatar Aug 18 '22 16:08 tyko0x

yeah that's a good find! setQueryData does take options as 3rd argument, but we would need to be able to pass:

  • queryKeyHashFn
  • queryHash

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

TkDodo avatar Aug 18 '22 19:08 TkDodo

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?

aboqasem avatar Aug 25 '22 10:08 aboqasem

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?

TkDodo avatar Aug 26 '22 09:08 TkDodo

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 avatar Aug 26 '22 18:08 aboqasem

@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.

TkDodo avatar Aug 26 '22 19:08 TkDodo

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.

TkDodo avatar Oct 30 '22 08:10 TkDodo

Thanks for checking. I ended up setting the global fn which ultimately works better I think.

tyko0x avatar Oct 31 '22 21:10 tyko0x