`TData` generic can be inferred or specified incorrectly, causing unexpected runtime errors.
Describe the bug
Related to https://github.com/TanStack/query/issues/4770, revitalizing since NoInfer/overloads can at least partially help.
-
TQueryFnDatais directly inferred fromqueryFnspecified in the hook optionsoptions -
TDatadefaults toTQueryFnData, but can be specified via a generic or annotated return type - When
TDatageneric is populated (by inference, or explicitly) , nothing enforces that there's type compatibility until you put aselectfunction, leading to uncaught runtime breaks.
Your minimal, reproducible example
https://codesandbox.io/p/devbox/vigilant-forest-7j45zd?file=%2Fsrc%2Findex.tsx%3A58%2C1
Steps to reproduce
See that usePosts and usePosts2 both have a data type claiming to be number, but are instead a Post at runtime.
Examples:
function usePosts() {
return useQuery<Post[], Error, number, string[]>({
queryKey: ['posts'],
queryFn: async (): Promise<Array<Post>> => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts'
);
return await response.json();
},
});
}
function usePosts2(): UseQueryResult<number, Error> {
return useQuery({
queryKey: ['posts'],
queryFn: async (): Promise<Array<Post>> => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts'
);
return await response.json();
},
});
}
Expected behavior
I expect to get a type-error, because I haven't specified a select function and TQueryFnData does not equal TData, therefore we have easy, uncaught runtime breaks.
Potential solutions:
- Using
NoInferon theUseQueryResult<NoInfer<TData>, TError>, will at least preventusePost2issue - We should be able to write a conditional type by using additional overloads (untested/hypothetically) to narrow on the condition where we have a
selectspecified.
type UseQueryOptionsWithSelect = ...
type UseQueryOptionsWithoutSelect = ...
declare function useQuery<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(options: UseQueryOptionsWithoutSelect<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseQueryResult<TQueryFnData extends TData ? NoInfer<TData> : never, TError>;
declare function useQuery<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(options: UseQueryOptionsWithSelect<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseQueryResult<NoInfer<TData>, TError>;
How often does this bug happen?
None
Screenshots or Videos
No response
Platform
N/A
Tanstack Query adapter
None
TanStack Query version
5.66.0
TypeScript version
5.7.2
Additional context
No response
Using NoInfer on the UseQueryResult<NoInfer<TData>, TError>, will at least prevent usePost2 issue
If that fixes it, please open a PR with that fix, including the appropriate type tests.
We should be able to write a conditional type by using additional overloads
We don’t want more overloads. If you add angle brackets <> to useQuery, you are basically doing a type assertion. I don’t want to add more complexity for that, as it’s not a recommended thing to do, as documented here: https://tanstack.com/query/v5/docs/framework/react/typescript
Started draft PR here https://github.com/TanStack/query/pull/8654 -- just indexed on useQuery to get first thoughts/before digging into how to more generally apply NoInfer to query-core types.
I understood the point to be that mismatches between TData and TQueryFnData don't raise a type error.
However, when I actually run the above code in the code sandbox, I get a type mismatch error in the return part, and when I run the build, I get an error and it doesn't build. Please let me know if there's something I'm missing.