T
TanStack11mo ago
manual-pink

No easier way to infer select?

Yo. Im using a queryOptions factory pattern, to manage all my queries. In that regard, i really would like to abstract some of the type logic away, into factory functions. Example:
export const useCustomers = createQuery(
queries.v1.getCustomers, // <- a function that returns queryOptions
);
export const useCustomers = createQuery(
queries.v1.getCustomers, // <- a function that returns queryOptions
);
I made it work purfectly with correct inheritance of the types. But it's really verbose! Verbose as in, "it feels wrong, so its wrong". Example of my implementation:
export function createQuery<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TArgs extends any[],
TQueryFnData = unknown,
TError = Error,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
queryOptionsFn: (
...args: TArgs
) => Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, "select">,
defaultOptions?: Partial<
Omit<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
"queryFn" | "queryKey"
>
>,
) {
return <TSelectedData = TData>(
args: TArgs,
options?: Partial<
Omit<
UseQueryOptions<TQueryFnData, TError, TSelectedData, TQueryKey>,
"queryFn" | "queryKey"
>
>,
): UseQueryResult<TSelectedData, TError> => {
const queryOptions = {
...queryOptionsFn(...args),
...defaultOptions,
...options,
} as UseQueryOptions<TQueryFnData, TError, TSelectedData, TQueryKey>;

return useQuery(queryOptions);
};
}
export function createQuery<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TArgs extends any[],
TQueryFnData = unknown,
TError = Error,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
queryOptionsFn: (
...args: TArgs
) => Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, "select">,
defaultOptions?: Partial<
Omit<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
"queryFn" | "queryKey"
>
>,
) {
return <TSelectedData = TData>(
args: TArgs,
options?: Partial<
Omit<
UseQueryOptions<TQueryFnData, TError, TSelectedData, TQueryKey>,
"queryFn" | "queryKey"
>
>,
): UseQueryResult<TSelectedData, TError> => {
const queryOptions = {
...queryOptionsFn(...args),
...defaultOptions,
...options,
} as UseQueryOptions<TQueryFnData, TError, TSelectedData, TQueryKey>;

return useQuery(queryOptions);
};
}
Is there really no easier way to get the wanted functionality, while allowing for type inference? (particular the select is hard to get inferred, in cases like this)
7 Replies
metropolitan-bronze
metropolitan-bronze11mo ago
I'd feel just doing this:
useQuery({
...queries.v1.getCustomers(getCustomerOpts),
select: whateverSelect,
})
useQuery({
...queries.v1.getCustomers(getCustomerOpts),
select: whateverSelect,
})
In the component would work just as well in your case. Not sure what the verbosity of the factory higher order functions gets you.
conscious-sapphire
conscious-sapphire11mo ago
yes the way proposed by DogPawHat is probably the way to go (and I know your pain, I went through the same implementation at least 2 or 3 times).e
conscious-sapphire
conscious-sapphire11mo ago
conscious-sapphire
conscious-sapphire11mo ago
the inference will still work properly
harsh-harlequin
harsh-harlequin11mo ago
this is what I also recommend, and it's how we've written our type-tests as well to ensure this pattern doesn't break: https://github.com/TanStack/query/blob/ab1a353ae4645905b5ed89ab7c7bb1d643e7b75d/packages/react-query/src/__tests__/useQuery.test-d.tsx#L36-L48 if someone wants to update the docs to highlight this pattern, please do 🙏
GitHub
query/packages/react-query/src/tests/useQuery.test-d.tsx at ab1...
🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query. - TanStack/query
manual-pink
manual-pinkOP11mo ago
Thanks yall! We wen't with the pattern you suggested 🙂
metropolitan-bronze
metropolitan-bronze11mo ago
Query Options | TanStack Query React Docs
One of the best ways to share queryKey and queryFn between multiple places, yet keep them co-located to one another, is to use the queryOptions helper. At runtime, this helper just returns whatever yo...

Did you find this page helpful?