const todos = createQueryOptions(['todos'], {
getById(id: number) {
return {
queryKey: [id],
queryFn({ signal }) {
return ky.get(...)
}
}
}
})
useQuery(todos.getById(1))
useQuery({
...todos.getById(1),
// other options here
select(response) { // inferred from queryFn
}
})
queryClient.invalidate({ queryKey: todos.getById(1).queryKey }) // ['todos', 'getById', 1]
queryClient.invalidate({ queryKey: todos.getById.queryKey }) // ['todos', 'getById']
queryClient.invalidate({ queryKey: todos.queryKey }) // ['todos', 'getById']
Created
February 24, 2026 15:54
-
-
Save trangcongthanh/87f2e68af342c95329ef3ad0e3301a7d to your computer and use it in GitHub Desktop.
Create Query Options
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { | |
| DataTag, | |
| QueryFunction, | |
| QueryFunctionContext, | |
| } from '@tanstack/react-query' | |
| const PRIMARY_KEY = 'queryKey' as const | |
| type Config = (...args: any) => { | |
| queryKey: any | |
| queryFn(context: QueryFunctionContext): any | |
| staleTime?: number | |
| gcTime?: number | |
| children?: Record<string, Config> | |
| } | |
| type CreateQueryOptionsResult< | |
| TRoot extends Array<string>, | |
| TConfigs extends Record<string, Config>, | |
| > = { | |
| queryKey: [...TRoot] | |
| } & { | |
| [TKey in keyof TConfigs]: { | |
| queryKey: [...TRoot, TKey] | |
| } & (< | |
| TArgs extends Parameters<TConfigs[TKey]>, | |
| TData extends Awaited<ReturnType<ReturnType<TConfigs[TKey]>['queryFn']>>, | |
| TPageParam extends Record<string, any> | never, | |
| TChildren extends ReturnType<TConfigs[TKey]>['children'], | |
| >( | |
| ...args: TArgs | |
| ) => { | |
| queryFn: QueryFunction<TData, [...TRoot, TKey, ...TArgs], TPageParam> | |
| staleTime?: number | |
| gcTime?: number | |
| } & { | |
| queryKey: DataTag<[...TRoot, TKey, ...TArgs], TData> | |
| } & ([TChildren] extends [undefined] | |
| ? never | |
| : CreateQueryOptionsResult< | |
| Array<string>, | |
| // @ts-expect-error Don't know | |
| TChildren | |
| >)) | |
| } | |
| export function createQueryOptions< | |
| TRoot extends Array<string>, | |
| TConfigs extends Record<string, Config>, | |
| >(root: TRoot, configs: TConfigs) { | |
| const options = {} as CreateQueryOptionsResult<TRoot, TConfigs> | |
| for (const [name, fn] of Object.entries(configs)) { | |
| if (name === PRIMARY_KEY) { | |
| throw new Error( | |
| `${name} is reserved for the "CreateQueryOptions" function`, | |
| ) | |
| } | |
| const handler = (...args: Array<any>) => { | |
| const { queryKey, children, queryFn, staleTime, gcTime } = fn(...args) | |
| const queryOption = { | |
| queryKey: [...root, name, ...queryKey], | |
| queryFn, | |
| ...(children && | |
| createQueryOptions([...root, name, ...queryKey], children)), | |
| } as any | |
| if (typeof staleTime === 'number') { | |
| queryOption.staleTime = staleTime | |
| } | |
| if (typeof gcTime === 'number') { | |
| queryOption.gcTime = gcTime | |
| } | |
| return queryOption | |
| } | |
| Object.defineProperty(handler, PRIMARY_KEY, { | |
| value: [...root, name], | |
| writable: false, | |
| }) | |
| Object.defineProperty(options, name, { | |
| value: handler, | |
| writable: false, | |
| }) | |
| } | |
| Object.defineProperty(options, PRIMARY_KEY, { | |
| value: root, | |
| writable: false, | |
| }) | |
| return options | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment