T
TanStack•10mo ago
quickest-silver

setQueriesData doesn't match query

Hello people, So uh I wanted to have multi-sorting in a table and the order of the sorting keys is important for my api so I pretty much stole your function and did something like this:
export const hashKey = (queryKey: QueryKey | MutationKey): string => {
return JSON.stringify(queryKey, (keyName, val) => {
if (_isPlainObject(val)) {
if (keyName === 'sorting') {
return Object.keys(val).reduce((result, key) => {
result[key] = val[key];
return result;
}, {} as any);
} else {
return Object.keys(val)
.sort()
.reduce((result, key) => {
result[key] = val[key];
return result;
}, {} as any);
}
} else {
return val;
}
});
};
export const hashKey = (queryKey: QueryKey | MutationKey): string => {
return JSON.stringify(queryKey, (keyName, val) => {
if (_isPlainObject(val)) {
if (keyName === 'sorting') {
return Object.keys(val).reduce((result, key) => {
result[key] = val[key];
return result;
}, {} as any);
} else {
return Object.keys(val)
.sort()
.reduce((result, key) => {
result[key] = val[key];
return result;
}, {} as any);
}
} else {
return val;
}
});
};
And then used it as a queryKeyHashFn. The problem is that when I try to do queryClient.setQueriesData it doesn't match it when sorting keys order differs. I checked a lil bit the source code and seems like it uses this function
export function matchQuery(
filters: QueryFilters,
query: Query<any, any, any, any>,
): boolean { ... }
export function matchQuery(
filters: QueryFilters,
query: Query<any, any, any, any>,
): boolean { ... }
queryClient.setQueriesData(
{ queryKey: [TABLE_QUERY_KEY, CREW_SEAMEN_TABLE_KEY], exact: false },
(cachedData: { data: CrewSeaman[] } | undefined) => {
console.log('cachedData', cachedData);
console.log('updatedSeaman', updatedSeaman);
if (!cachedData || !cachedData.data.length) return cachedData;

return {
...cachedData,
data: cachedData.data.map(seaman =>
seaman.id === updatedSeaman.id ? { ...updatedSeaman, active: !seaman.active } : seaman
)
};
queryClient.setQueriesData(
{ queryKey: [TABLE_QUERY_KEY, CREW_SEAMEN_TABLE_KEY], exact: false },
(cachedData: { data: CrewSeaman[] } | undefined) => {
console.log('cachedData', cachedData);
console.log('updatedSeaman', updatedSeaman);
if (!cachedData || !cachedData.data.length) return cachedData;

return {
...cachedData,
data: cachedData.data.map(seaman =>
seaman.id === updatedSeaman.id ? { ...updatedSeaman, active: !seaman.active } : seaman
)
};
Here is and how I call it. I suppose the only difference is the isPlainObject where I used the lodash's one. Idk if that's the problem
26 Replies
quickest-silver
quickest-silverOP•10mo ago
Hmm looks like predicate matches only one query but I still get no data in the updater function
quickest-silver
quickest-silverOP•10mo ago
No description
quickest-silver
quickest-silverOP•10mo ago
But it has data:
No description
sensitive-blue
sensitive-blue•10mo ago
👋
sensitive-blue
sensitive-blue•10mo ago
@TkDodo 🔮 Hi, can you help us out with this one? A brief description of the issue: queryKey has a large filter object in it. When the default hashFn is used everything works fine and data is returned inside queryClient.setQueriesData, but we need to use a custom hashFn. That's when things become weird.
No description
No description
sensitive-blue
sensitive-blue•10mo ago
As you can see with a custom hashFn there's no data being returned. Even though it should partially match the beginning of the queryKey ["table", "pms_jobs_list"]. Sorry for pinging you, but that's the second time I encountered this issue this week. Hopefully, you can help us to fix it.
No description
ambitious-aqua
ambitious-aqua•10mo ago
Where is the custom hashFn defined? If it's on useQuery then it can't match. There's an open issue about it I would set the custom hashFn as a default
sensitive-blue
sensitive-blue•10mo ago
@Nikos 👆
quickest-silver
quickest-silverOP•10mo ago
ah Yes it is there... aight I will find a workaround
sensitive-blue
sensitive-blue•10mo ago
So to get things clear. It can not match because hashFn is defined on a useQuery and somewhere in the RQ code it uses the default hashFn for the match instead of the custom one? Do I get this right?
ambitious-aqua
ambitious-aqua•10mo ago
yes, that's right
ambitious-aqua
ambitious-aqua•10mo ago
GitHub
queryClient.setQueryData does not support queryKeyHashFn · Issue #2...
Describe the bug Currently if we use queryKeyHashFn to customize the has key, in useQuery hook, it will result a string key. However when we want to update the client (cached) data for that key usi...
ambitious-aqua
ambitious-aqua•10mo ago
ah no, that's a different issue. So I looked up the code and unless you are passing exact:true, the hashFn shouldn't matter. can you please show a quick minimal reproduction?
quickest-silver
quickest-silverOP•10mo ago
sure gimme 1 min You will need an active query like this:
export const hashKey = (queryKey: QueryKey | MutationKey): string => {
return JSON.stringify(queryKey, (keyName, val) => {
if (_isPlainObject(val)) {
if (keyName === 'sorting') {
return Object.keys(val).reduce((result, key) => {
result[key] = val[key];
return result;
}, {} as any);
} else {
return Object.keys(val)
.sort()
.reduce((result, key) => {
result[key] = val[key];
return result;
}, {} as any);
}
} else {
return val;
}
});
};

// query options
{
queryKey: ['test', 'test2', {someOtherParams: ['hello'], sorting: {b: 'asc', a: 'asc'}}],
queryFn: () => fetchingFn({ ...requestParams, label }, fetchingFnRequestParams), // problably a promise here that returns some data
// queryKeyHashFn: hashKey, // uncomment this to check the bug
retry: false,
select: selectTableQueryState<TData>, // I have select but it should matter if you remove it.
enabled: isEnabled, // probably this shouldn't matter either if you remove it
staleTime: 5 * 60 * 1000
}
export const hashKey = (queryKey: QueryKey | MutationKey): string => {
return JSON.stringify(queryKey, (keyName, val) => {
if (_isPlainObject(val)) {
if (keyName === 'sorting') {
return Object.keys(val).reduce((result, key) => {
result[key] = val[key];
return result;
}, {} as any);
} else {
return Object.keys(val)
.sort()
.reduce((result, key) => {
result[key] = val[key];
return result;
}, {} as any);
}
} else {
return val;
}
});
};

// query options
{
queryKey: ['test', 'test2', {someOtherParams: ['hello'], sorting: {b: 'asc', a: 'asc'}}],
queryFn: () => fetchingFn({ ...requestParams, label }, fetchingFnRequestParams), // problably a promise here that returns some data
// queryKeyHashFn: hashKey, // uncomment this to check the bug
retry: false,
select: selectTableQueryState<TData>, // I have select but it should matter if you remove it.
enabled: isEnabled, // probably this shouldn't matter either if you remove it
staleTime: 5 * 60 * 1000
}
I used the custom hashKey to remove the ordering for my sorting key and then I try to match it with:
queryClient.setQueriesData(
{
queryKey: ['test', 'test2'],
exact: false,
type: 'active',
predicate: pr => {
console.log('shit2', pr); // the query appears here
}
},
queryData => {
console.log('shit', queryData); // no data is printed when queryKeyHashFn is custom.
);
}
queryClient.setQueriesData(
{
queryKey: ['test', 'test2'],
exact: false,
type: 'active',
predicate: pr => {
console.log('shit2', pr); // the query appears here
}
},
queryData => {
console.log('shit', queryData); // no data is printed when queryKeyHashFn is custom.
);
}
not sure if this is clear enough @TkDodo 🔮, i hope it is
ambitious-aqua
ambitious-aqua•10mo ago
predicate needs to return true/false. the log statement without a return destroys this
quickest-silver
quickest-silverOP•10mo ago
yes my bad i just removed it now
ambitious-aqua
ambitious-aqua•10mo ago
I can only look into this further if you put the sample code into a stackblitz or codesandbox
quickest-silver
quickest-silverOP•10mo ago
i do return true
ambitious-aqua
ambitious-aqua•10mo ago
I need to see and run it and potentially debug it to see what's going wrong
quickest-silver
quickest-silverOP•10mo ago
queryClient.setQueriesData(
{
queryKey: ['table', 'pms_jobs_list'],
exact: false,
type: 'active',
predicate: pr => {
console.log('shit2', pr);
return true;
}
},
queryData => {
console.log('shit', queryData);
if (queryData?.data) {
return {
...queryData,
data: queryData.data.map(job => {
if (job.id === id) return { ...job, ...params };
return job;
})
};
}

return queryData;
}
);
queryClient.setQueriesData(
{
queryKey: ['table', 'pms_jobs_list'],
exact: false,
type: 'active',
predicate: pr => {
console.log('shit2', pr);
return true;
}
},
queryData => {
console.log('shit', queryData);
if (queryData?.data) {
return {
...queryData,
data: queryData.data.map(job => {
if (job.id === id) return { ...job, ...params };
return job;
})
};
}

return queryData;
}
);
This was the actual code alright I will try to reproduce it there
quickest-silver
quickest-silverOP•10mo ago
StackBlitz
Query Simple Example - StackBlitz
Run official live example code for Query Simple, created by Tan Stack on StackBlitz
quickest-silver
quickest-silverOP•10mo ago
check this out click the button check console and then comment the custom hashingFn and click the button again and check the console Ah i think I know whats going on:
setQueriesData<TQueryFnData>(
filters: QueryFilters,
updater: Updater<TQueryFnData | undefined, TQueryFnData | undefined>,
options?: SetDataOptions,
): Array<[QueryKey, TQueryFnData | undefined]> {
return notifyManager.batch(() =>
this.#queryCache
.findAll(filters) // This should be matching correctly
.map(({ queryKey }) => [
queryKey,
this.setQueryData<TQueryFnData>(queryKey, updater, options), // Here must be the problem
]),
)
}
setQueriesData<TQueryFnData>(
filters: QueryFilters,
updater: Updater<TQueryFnData | undefined, TQueryFnData | undefined>,
options?: SetDataOptions,
): Array<[QueryKey, TQueryFnData | undefined]> {
return notifyManager.batch(() =>
this.#queryCache
.findAll(filters) // This should be matching correctly
.map(({ queryKey }) => [
queryKey,
this.setQueryData<TQueryFnData>(queryKey, updater, options), // Here must be the problem
]),
)
}
setQueryData<
TQueryFnData = unknown,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
unknown,
infer TaggedValue
>
? TaggedValue
: TQueryFnData,
>(
queryKey: TTaggedQueryKey,
updater: Updater<
NoInfer<TInferredQueryFnData> | undefined,
NoInfer<TInferredQueryFnData> | undefined
>,
options?: SetDataOptions,
): TInferredQueryFnData | undefined {
const defaultedOptions = this.defaultQueryOptions< // Here you get the default options so it doesn't contain my hashing fn.
any,
any,
unknown,
any,
QueryKey
>({ queryKey })

const query = this.#queryCache.get<TInferredQueryFnData>( // So then this query will be undefined or null or idk you get the point
defaultedOptions.queryHash,
)
const prevData = query?.state.data
const data = functionalUpdate(updater, prevData)

if (data === undefined) {
return undefined
}

return this.#queryCache
.build(this, defaultedOptions)
.setData(data, { ...options, manual: true })
}
setQueryData<
TQueryFnData = unknown,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
unknown,
infer TaggedValue
>
? TaggedValue
: TQueryFnData,
>(
queryKey: TTaggedQueryKey,
updater: Updater<
NoInfer<TInferredQueryFnData> | undefined,
NoInfer<TInferredQueryFnData> | undefined
>,
options?: SetDataOptions,
): TInferredQueryFnData | undefined {
const defaultedOptions = this.defaultQueryOptions< // Here you get the default options so it doesn't contain my hashing fn.
any,
any,
unknown,
any,
QueryKey
>({ queryKey })

const query = this.#queryCache.get<TInferredQueryFnData>( // So then this query will be undefined or null or idk you get the point
defaultedOptions.queryHash,
)
const prevData = query?.state.data
const data = functionalUpdate(updater, prevData)

if (data === undefined) {
return undefined
}

return this.#queryCache
.build(this, defaultedOptions)
.setData(data, { ...options, manual: true })
}
not sure tho
ambitious-aqua
ambitious-aqua•10mo ago
yeah your're right, the problem is that for the updater function, we need to find the existing query. for that, we need the hashing-fn. But since it's only defined on the query itself, we don't have it. setting as a default side-steps that problem otherwise, we would need to have setQueryData accept a hashFn, but that won't work if you match differetn queries which all need different hashFns the default (globally) or per query with queryClient.setQueryDefaults is the best approach imo
quickest-silver
quickest-silverOP•10mo ago
queryClient.setQueryDefaults is the best approach imo
queryClient.setQueryDefaults is the best approach imo
Yes if my opinion matters this looks like the best approach to me I could try to do this but I am not really familiar with the project
ambitious-aqua
ambitious-aqua•10mo ago
queryClient.setQueryDefaults(['test'], { queryKeyHashFn: yourFn })
quickest-silver
quickest-silverOP•10mo ago
yes looks pretty nice! ohh nvm just realised it exist damn I am dump thank you very much!

Did you find this page helpful?