Skip to content

Suggestion: improve ergonomics of *InfiniteOptions types #8477

@OliverJAsh

Description

@OliverJAsh

Describe the bug

The docs suggest using infiniteQueryOptions to abstract infinite query options. For example:

import { infiniteQueryOptions } from '@tanstack/react-query';

export const queryOptions = infiniteQueryOptions({
  queryKey: ['foo'],
  queryFn: ({ pageParam }) => Promise.resolve({ foo: 'bar' }),
  initialPageParam: 1,
  getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,
});

However, when using TypeScript's isolated declarations (isolatedDeclarations), all module exports must have type annotations. For this reason it would be preferable to use a type annotation rather than the infiniteQueryOptions type constructor.

The problem with this, however, is it's very verbose:

import type {
  DefaultError,
  InfiniteData,
  QueryKey,
  UnusedSkipTokenInfiniteOptions,
} from '@tanstack/react-query';

type MyResponse = {
  foo: string;
};

export const queryOptions: UnusedSkipTokenInfiniteOptions<
  MyResponse,
  DefaultError,
  InfiniteData<MyResponse, number>,
  QueryKey,
  number
> = {
  queryKey: ['foo'],
  queryFn: ({ pageParam }) => Promise.resolve({ foo: 'bar' }),
  initialPageParam: 1,
  getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,
};

Except for the first and last type parameters provided to UnusedSkipTokenInfiniteOptions, all the others could theoretically just use the defaults. However, we're forced to provide them because we need to provide TPageParam (the last type parameter), otherwise it would default to unknown.

Given more TypeScript users will likely be looking to enable isolated declarations in the future and thus be required to add type annotations, I wonder if we could make these types more ergonomic.

One idea would be to move the position of the TPageParam type parameter in the *InfiniteOptions types, so we can benefit from defaults. For example, if it was moved from last to second position then the example above would look much better:

import type {
  DefaultError,
  InfiniteData,
  QueryKey,
  UnusedSkipTokenInfiniteOptions,
} from '@tanstack/react-query';

type MyResponse = {
  foo: string;
};

export const queryOptions: UnusedSkipTokenInfiniteOptions<
  MyResponse,
  number
> = {
  queryKey: ['foo'],
  queryFn: ({ pageParam }) => Promise.resolve({ foo: 'bar' }),
  initialPageParam: 1,
  getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,
};
-type UnusedSkipTokenInfiniteOptions<TQueryFnData, TError = DefaultError, TData = InfiniteData<TQueryFnData>, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown> = OmitKeyof<UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey, TPageParam>, 'queryFn'> & {
    queryFn?: Exclude<UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey, TPageParam>['queryFn'], SkipToken | undefined>;
};
+type UnusedSkipTokenInfiniteOptions<TQueryFnData, TPageParam = unknown, TError = DefaultError, TData = InfiniteData<TQueryFnData, TPageParam>, TQueryKey extends QueryKey = QueryKey> = OmitKeyof<UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey, TPageParam>, 'queryFn'> & {
    queryFn?: Exclude<UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey, TPageParam>['queryFn'], SkipToken | undefined>;
};

Your minimal, reproducible example

N/A

Steps to reproduce

N/A

Expected behavior

N/A

How often does this bug happen?

None

Screenshots or Videos

No response

Platform

N/A

Tanstack Query adapter

None

TanStack Query version

N/A

TypeScript version

No response

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions