Skip to content

Instantly share code, notes, and snippets.

@trangcongthanh
Created February 24, 2026 15:54
Show Gist options
  • Select an option

  • Save trangcongthanh/87f2e68af342c95329ef3ad0e3301a7d to your computer and use it in GitHub Desktop.

Select an option

Save trangcongthanh/87f2e68af342c95329ef3ad0e3301a7d to your computer and use it in GitHub Desktop.
Create Query Options
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']
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