Skip to content

TData generic can be inferred or specified incorrectly, causing unexpected runtime errors. #8639

Open
@braeden

Description

@braeden

Describe the bug

Related to #4770, revitalizing since NoInfer/overloads can at least partially help.

  • TQueryFnData is directly inferred from queryFn specified in the hook options options
  • TData defaults to TQueryFnData, but can be specified via a generic or annotated return type
  • When TData generic is populated (by inference, or explicitly) , nothing enforces that there's type compatibility until you put a select function, 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 NoInfer on the UseQueryResult<NoInfer<TData>, TError>, will at least prevent usePost2 issue
  • We should be able to write a conditional type by using additional overloads (untested/hypothetically) to narrow on the condition where we have a select specified.
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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions