T
TanStack9mo ago
genetic-orange

Can I normalize every queryKey globally?

I have an issue where queryKey is used inconsistently across the project—it can be an object, an array, etc. To standardize it, I created a function normalizeQueryKey that sorts each key and converts it into a string. For example:
const keys = ["user", 123, true, null, undefined, { id: 1, name: "Test" }, { name: "Test", id: 1 }, NaN, Infinity, -Infinity, new Map()];
normalizeQueryKey(keys);
// Returns: ['user', '123', 'true', 'null', 'undefined', '{"id":"1","name":"Test"}', '{"id":"1","name":"Test"}', 'NaN', 'Infinity', '-Infinity', 'NaN', '{}']

const cyclicObj: any = { a: 1 };
cyclicObj.b = cyclicObj;
normalizeQueryKey(["user", cyclicObj]);
// Returns: ['user', '{"a":"1","b":"[Circular]"}']
const keys = ["user", 123, true, null, undefined, { id: 1, name: "Test" }, { name: "Test", id: 1 }, NaN, Infinity, -Infinity, new Map()];
normalizeQueryKey(keys);
// Returns: ['user', '123', 'true', 'null', 'undefined', '{"id":"1","name":"Test"}', '{"id":"1","name":"Test"}', 'NaN', 'Infinity', '-Infinity', 'NaN', '{}']

const cyclicObj: any = { a: 1 };
cyclicObj.b = cyclicObj;
normalizeQueryKey(["user", cyclicObj]);
// Returns: ['user', '{"a":"1","b":"[Circular]"}']
Now, I manually normalize every queryKey. ❓ My question is: Can I normalize every queryKey globally? I tried using queryKeyHashFn, but I noticed that queryKey remains unchanged—it only formats queryHash. As a result, if queryKey is different, TanStack Query still triggers a refetch. So, is there a way to normalize every queryKey globally?
9 Replies
like-gold
like-gold9mo ago
if queryKey is different, TanStack Query still triggers a refetch.
customizing the hash should be enough
genetic-orange
genetic-orangeOP9mo ago
@TkDodo 🔮 Are you saying that TanStack Query actually uses the hash (queryHash) internally for lookups and deduplication? Because from my observations, it seems to rely on queryKey. Can you clarify?
like-gold
like-gold9mo ago
Yes it uses the hash, what else should the hash be used for?
genetic-orange
genetic-orangeOP9mo ago
I'm doing this:
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
queryKeyHashFn: (queryKey) => {
const res = JSON.stringify(
normalizeQueryKeys(queryKey as AnyType[]),
);
if (res.includes('"hotel"')) {
console.log('### res: ', res); // ["hotel","1","..."]
}
return res;
},
},
},
}),
);
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
queryKeyHashFn: (queryKey) => {
const res = JSON.stringify(
normalizeQueryKeys(queryKey as AnyType[]),
);
if (res.includes('"hotel"')) {
console.log('### res: ', res); // ["hotel","1","..."]
}
return res;
},
},
},
}),
);
Then I check it:
const queryCache = queryClient.getQueryCache();
console.log('### queryCache: ', queryCache);
const queryCache = queryClient.getQueryCache();
console.log('### queryCache: ', queryCache);
And I see that the queryHash is different! I think the queryKeyHashFn isn't working the way I expect — because I hope that when 1 or "1" comes, they should be treated as the same value. In this case, I could just change 1 to "1" and that would be a solution, but I have many places where I use objects and arrays that I normalize with normalizeQueryKeys to ensure they have the same structure. What am I doing wrong, or how can I globally normalize queryKeys or queryHash?
No description
like-gold
like-gold9mo ago
because I hope that when 1 or "1" comes, they should be treated as the same value
if you want that you need to do this yourself in the queryKeyHashFn.
genetic-orange
genetic-orangeOP9mo ago
I apologize, but that's exactly what I'm doing! However, it's not working for me! I can't understand why.
like-gold
like-gold9mo ago
then show a minimal reproduction please (stackblitz, codesandbox)
genetic-orange
genetic-orangeOP9mo ago
I found what the problem was. I was creating a new QueryClient() on SSR and doing prefetchQuery. Then, on the client, I had this:
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
queryKeyHashFn: (queryKey) => {
const res = JSON.stringify(
normalizeQueryKeys(queryKey as AnyType[]),
);
return res;
},
},
},
})
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
queryKeyHashFn: (queryKey) => {
const res = JSON.stringify(
normalizeQueryKeys(queryKey as AnyType[]),
);
return res;
},
},
},
})
Because I had two different QueryClient instances and there was no queryKeyHashFn on SSR, this problem occurred. The solution was to extract the config with queryKeyHashFn into a variable and use it in all the necessary places (on SSR, etc.).
like-gold
like-gold9mo ago
well, there you go

Did you find this page helpful?