Describe the bug
Current typings of useQuery (also prefetchQuery and fetchQuery) support string-only query keys.
It seems not possible to pass a custom type (for array keys).
To Reproduce
https://codesandbox.io/s/objective-nobel-8incc?file=/src/App.tsx
Expected behavior
It should be possible to pass a generic type for query keys.
Desktop
not sure what you mean. You can pass anything serializable as a queryKey. Have you read the migration guide, especially:
https://react-query.tanstack.com/guides/migrating-to-react-query-3#query-key-partspieces-are-no-longer-automatically-spread-to-the-query-function
react-query will no longer "inject" the queryKey to your fetchFn. It is recommended to use inline functions, especially with typeScript to get typesafety:
const fetchContentById = async (id: number) => ...
...
const id = 1;
const query = useQuery(["contentById", id], () => fetchContentById(id));
Hi @TkDodo , thanks for your answer.

This is basically what I want to achieve with TypeScript:
https://react-query.tanstack.com/guides/query-functions#query-function-variables
Ah yes, I think you are right. I haven't actually used the context before, the types are defined like this:
export declare type QueryKey = string | unknown[];
export declare type QueryFunction<T = unknown> = (context: QueryFunctionContext) => T | Promise<T>;
export interface QueryFunctionContext<TQueryKey extends QueryKey = QueryKey, TPageParam = any> {
queryKey: TQueryKey;
pageParam?: TPageParam;
}
so the QueryFunction only takes a QueryFunctionContext without generic, I think we might need to change that to:
export declare type QueryKey = string | unknown[];
- export declare type QueryFunction<T = unknown> = (context: QueryFunctionContext) => T | Promise<T>;
+ export declare type QueryFunction<T = unknown, TQueryKey extends QueryKey = QueryKey> = (context: QueryFunctionContext<QueryKey>) => T | Promise<T>;
export interface QueryFunctionContext<TQueryKey extends QueryKey = QueryKey, TPageParam = any> {
queryKey: TQueryKey;
pageParam?: TPageParam;
}
I cannot change the type declarations in codesandbox to try if this actually works, maybe you can try it out?
Thanks, this change allows to pass the QueryKey type to QueryFunction.
But useQuery does not allow it:
https://codesandbox.io/s/xenodochial-burnell-fds01?file=/src/App.tsx

I guess there's the same issue with prefetchQuery and fetchQuery.
I run today into the same problem during the migration from v2 to v3. I guess the examples in the documentation assume that you use the useQuery hook along with the queryFn in one file. For us this is not the case as we have the useQuery hook in another place than the queryFn. This let us not simply use a variable inside the function, rather we need to pass the QueryFunctionContext and destruct it manually which is working fine without TS. I got it now also working with the TS compiler in the following way:
/**
* This is in a dir called hooks e.g.
* hooks/userChangeLogQuery.ts
*/
import { useQuery, UseQueryResult } from 'react-query';
import {
queryChangeLog,
QueriedChangeLog,
} from '@ui/reactQuery/queryFunctions/queryChangeLog';
import { GraphQLResponse } from '@ui/axios/requestGraphQL';
export const userChangeLogQuery = (
id: string
): UseQueryResult<GraphQLResponse<QueriedChangeLog>, Error> =>
useQuery(
['CHANGELOG_QUERY', { id }],
queryChangeLog
);
and here is the corresponding query function:
/**
* This is in a dir called queryFunction e.g.
* queryFunction/queryChangeLog.ts
*/
import { QueryFunctionContext } from 'react-query';
import { requestGraphQL, GraphQLResponse } from '@ui/axios/axios/requestGraphQL';
import { ChangeLog } from '@server/src/graphql/types/generated';
export type QueriedChangeLog = {
getChangeLog: ChangeLog[];
};
export const queryChangeLog = async (
context: QueryFunctionContext
): Promise<GraphQLResponse<QueriedChangeLog>> => {
// If I would not do the type assertion here type it would work with our compiler settings.
const [, { id }] = context.queryKey as [string, { id: string }];
return requestGraphQL<QueriedChangeLog>(
/* GraphQL */ `
query getChangeLog($id: ID!) {
getChangeLog(id: $id) {
date_of_change
field
old_value
new_value
comment
}
}
`,
{
id
}
);
}
I guess if I would just use the id without wrapping it into an object I could maybe access the array inside the queryChangeLog function. An update of the documentation on this topic would be awesome. If you want I can try to dig into it and propose an PR. Also some hint if I'm on the right track would be awesome and also sorry for the long example.
Cheers 馃憢
I've made an attempt to make QueryKey generic in the codebase but with no success 馃槗
Tried to start small but inevitably ended up modifying lots of files as it's (QueryKey) used pretty much everywhere, and there are also lots of any that I needed to get rid of but failed to. e.g. https://github.com/tannerlinsley/react-query/blob/80cecef22c3e088d6cd9f8fbc5cd9e2c0aab962f/src/core/utils.ts#L91
I stopped because it led me nowhere, plus I don't know if it was the right thing to do though.
Anyway, for now I've used the workaround from @Kiesen 馃憤
e.g.
const getContentByIdQueryKey = (id: string) => {
return ['contentById', id];
};
const fetchContentById = async ({ queryKey }: QueryFunctionContext) => {
const [key, id] = queryKey as ReturnType<typeof getContentByIdQueryKey>;
// fetch data
return data;
};
export const useContentById = (id: string) => {
return useQuery(getContentByIdQueryKey(id), fetchContentById);
};
So why is this workaround better than using inline functions?
const getContentByIdQueryKey = (id: string) => {
return ['contentById', id];
};
const fetchContentById = async (id: string) => {
// fetch data
return data;
};
export const useContentById = (id: string) => {
return useQuery(getContentByIdQueryKey(id), () => fetchContentById(id));
};
it should be possible to always do that, because if you can build the query key, you can also pass the same arguments to your fetch function. everything that you need in the fetch function is now an explicit dependency :)
Hi @TkDodo,
I think it's just a matter of taste ;)
When using the QueryFunctionContext with a custom type, you get the intellisense and autocomplete for free when destructuring the key 馃槢
Anyway I'm good with the 2 solutions, I think we can close this issue 馃憤
Good morning. Thank you @TkDodo for pointing again to the inline function workaround. I just tested this in our codebase and it is obviously working. I think during the update I got a little bit lost with all the type warning / errors. I have to say I like the inline function way a little more than my proposed solution.
Cheers. 馃憢
Most helpful comment
Good morning. Thank you @TkDodo for pointing again to the inline function workaround. I just tested this in our codebase and it is obviously working. I think during the update I got a little bit lost with all the type warning / errors. I have to say I like the inline function way a little more than my proposed solution.
Cheers. 馃憢