Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix assorted infinite query types #4869

Merged
merged 4 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/rtk-query/usage/infinite-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ This structure allows flexibility in how your UI chooses to render the data (sho

Infinite query endpoints are defined by returning an object inside the `endpoints` section of `createApi`, and defining the fields using the `build.infiniteQuery()` method. They are an extension of standard query endpoints - you can specify [the same options as standard queries](./queries.mdx#defining-query-endpoints) (providing either `query` or `queryFn`, customizing with `transformResponse`, lifecycles with `onCacheEntryAdded` and `onQueryStarted`, defining tags, etc). However, they also require an additional `infiniteQueryOptions` field to specify the infinite query behavior.

With TypeScript, you must supply 3 generic arguments: `build.infiniteQuery<ResultType, QueryArg, PageParam>`, where `ResultType` is the contents of a single page, `QueryArg` is the type passed in as the cache key, and `PageParam` is the value that will be passed to `query/queryFn` to make the rest. If there is no argument, use `void` for the arg type instead.
With TypeScript, you must supply 3 generic arguments: `build.infiniteQuery<ResultType, QueryArg, PageParam>`, where `ResultType` is the contents of a single page, `QueryArg` is the type passed in as the cache key, and `PageParam` is the value used to request a specific page. If there is no argument, use `void` for the arg type instead.

### `infiniteQueryOptions`

Expand Down
38 changes: 28 additions & 10 deletions packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
PageParamFrom,
QueryArgFrom,
QueryDefinition,
ResultDescription,
ResultTypeFrom,
} from '../endpointDefinitions'
import {
Expand Down Expand Up @@ -71,14 +72,14 @@ export type BuildThunksApiEndpointQuery<

export type BuildThunksApiEndpointInfiniteQuery<
Definition extends InfiniteQueryDefinition<any, any, any, any, any>,
> = Matchers<QueryThunk, Definition>
> = Matchers<InfiniteQueryThunk<any>, Definition>

export type BuildThunksApiEndpointMutation<
Definition extends MutationDefinition<any, any, any, any, any>,
> = Matchers<MutationThunk, Definition>

type EndpointThunk<
Thunk extends QueryThunk | MutationThunk,
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
Definition extends EndpointDefinition<any, any, any, any>,
> =
Definition extends EndpointDefinition<
Expand All @@ -94,27 +95,41 @@ type EndpointThunk<
ATConfig & { rejectValue: BaseQueryError<BaseQueryFn> }
>
: never
: never
: Definition extends InfiniteQueryDefinition<
infer QueryArg,
infer PageParam,
infer BaseQueryFn,
any,
infer ResultType
>
? Thunk extends AsyncThunk<unknown, infer ATArg, infer ATConfig>
? AsyncThunk<
InfiniteData<ResultType, PageParam>,
ATArg & { originalArgs: QueryArg },
ATConfig & { rejectValue: BaseQueryError<BaseQueryFn> }
>
: never
: never

export type PendingAction<
Thunk extends QueryThunk | MutationThunk,
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
Definition extends EndpointDefinition<any, any, any, any>,
> = ReturnType<EndpointThunk<Thunk, Definition>['pending']>

export type FulfilledAction<
Thunk extends QueryThunk | MutationThunk,
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
Definition extends EndpointDefinition<any, any, any, any>,
> = ReturnType<EndpointThunk<Thunk, Definition>['fulfilled']>

export type RejectedAction<
Thunk extends QueryThunk | MutationThunk,
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
Definition extends EndpointDefinition<any, any, any, any>,
> = ReturnType<EndpointThunk<Thunk, Definition>['rejected']>

export type Matcher<M> = (value: any) => value is M

export interface Matchers<
Thunk extends QueryThunk | MutationThunk,
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
Definition extends EndpointDefinition<any, any, any, any>,
> {
matchPending: Matcher<PendingAction<Thunk, Definition>>
Expand Down Expand Up @@ -645,7 +660,6 @@ export function buildThunks<
isForcedQueryNeedingRefetch || !cachedData ? blankData : cachedData
) as InfiniteData<unknown, unknown>


// If the thunk specified a direction and we do have at least one page,
// fetch the next or previous page
if ('direction' in arg && arg.direction && existingData.pages.length) {
Expand Down Expand Up @@ -960,14 +974,18 @@ export function getPreviousPageParam(

export function calculateProvidedByThunk(
action: UnwrapPromise<
ReturnType<ReturnType<QueryThunk>> | ReturnType<ReturnType<MutationThunk>>
| ReturnType<ReturnType<QueryThunk>>
| ReturnType<ReturnType<MutationThunk>>
| ReturnType<ReturnType<InfiniteQueryThunk<any>>>
>,
type: 'providesTags' | 'invalidatesTags',
endpointDefinitions: EndpointDefinitions,
assertTagType: AssertTagTypes,
) {
return calculateProvidedBy(
endpointDefinitions[action.meta.arg.endpointName][type],
endpointDefinitions[action.meta.arg.endpointName][
type
] as ResultDescription<any, any, any, any, any>,
isFulfilled(action) ? action.payload : undefined,
isRejectedWithValue(action) ? action.payload : undefined,
action.meta.arg.originalArgs,
Expand Down
2 changes: 1 addition & 1 deletion packages/toolkit/src/query/endpointDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ export interface InfiniteQueryExtraOptions<

providesTags?: ResultDescription<
TagTypes,
ResultType,
InfiniteData<ResultType, PageParam>,
QueryArg,
BaseQueryError<BaseQuery>,
BaseQueryMeta<BaseQuery>
Expand Down
4 changes: 2 additions & 2 deletions packages/toolkit/src/query/react/buildHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ export type UseInfiniteQuery<
export type UseInfiniteQueryState<
D extends InfiniteQueryDefinition<any, any, any, any, any>,
> = <R extends Record<string, any> = UseInfiniteQueryStateDefaultResult<D>>(
arg: QueryArgFrom<D> | SkipToken,
arg: InfiniteQueryArgFrom<D> | SkipToken,
options?: UseInfiniteQueryStateOptions<D, R>,
) => UseInfiniteQueryStateResult<D, R>

Expand Down Expand Up @@ -977,7 +977,7 @@ export type TypedUseInfiniteQueryState<
export type UseInfiniteQuerySubscription<
D extends InfiniteQueryDefinition<any, any, any, any, any>,
> = (
arg: QueryArgFrom<D> | SkipToken,
arg: InfiniteQueryArgFrom<D> | SkipToken,
options?: UseInfiniteQuerySubscriptionOptions<D>,
) => UseInfiniteQuerySubscriptionResult<D>

Expand Down
37 changes: 37 additions & 0 deletions packages/toolkit/src/query/tests/infiniteQueries.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
QueryStatus,
} from '@reduxjs/toolkit/query/react'
import { setupApiStore } from '../../tests/utils/helpers'
import { createSlice } from '@internal/createSlice'

describe('Infinite queries', () => {
test('Basic infinite query behavior', async () => {
Expand Down Expand Up @@ -54,6 +55,12 @@ describe('Infinite queries', () => {
InfiniteData<Pokemon[], number>
>()
},
providesTags: (result) => {
expectTypeOf(result).toEqualTypeOf<
InfiniteData<Pokemon[], number> | undefined
>()
return []
},
}),
}),
})
Expand All @@ -68,6 +75,36 @@ describe('Infinite queries', () => {

expectTypeOf(pokemonApi.useGetInfinitePokemonInfiniteQuery).toBeFunction()

expectTypeOf(pokemonApi.endpoints.getInfinitePokemon.useInfiniteQuery)
.parameter(0)
.toEqualTypeOf<string | typeof skipToken>()

expectTypeOf(pokemonApi.endpoints.getInfinitePokemon.useInfiniteQueryState)
.parameter(0)
.toEqualTypeOf<string | typeof skipToken>()

expectTypeOf(
pokemonApi.endpoints.getInfinitePokemon.useInfiniteQuerySubscription,
)
.parameter(0)
.toEqualTypeOf<string | typeof skipToken>()

const slice = createSlice({
name: 'pokemon',
initialState: {} as { data: Pokemon[] },
reducers: {},
extraReducers: (builder) => {
builder.addMatcher(
pokemonApi.endpoints.getInfinitePokemon.matchFulfilled,
(state, action) => {
expectTypeOf(action.payload).toEqualTypeOf<
InfiniteData<Pokemon[], number>
>()
},
)
},
})

const res = storeRef.store.dispatch(
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
)
Expand Down
Loading