Skip to content

Commit 66ff32a

Browse files
authored
Fix assorted infinite query types (#4869)
* Fix infinite query matcher types * Fix infinite query providesTags types * Fix infinite query subscription/state hooks * Fix internal providedBy type issue
1 parent 18ddd7e commit 66ff32a

File tree

5 files changed

+69
-14
lines changed

5 files changed

+69
-14
lines changed

docs/rtk-query/usage/infinite-queries.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ This structure allows flexibility in how your UI chooses to render the data (sho
6363

6464
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.
6565

66-
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.
66+
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.
6767

6868
### `infiniteQueryOptions`
6969

packages/toolkit/src/query/core/buildThunks.ts

+28-10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
PageParamFrom,
2727
QueryArgFrom,
2828
QueryDefinition,
29+
ResultDescription,
2930
ResultTypeFrom,
3031
} from '../endpointDefinitions'
3132
import {
@@ -71,14 +72,14 @@ export type BuildThunksApiEndpointQuery<
7172

7273
export type BuildThunksApiEndpointInfiniteQuery<
7374
Definition extends InfiniteQueryDefinition<any, any, any, any, any>,
74-
> = Matchers<QueryThunk, Definition>
75+
> = Matchers<InfiniteQueryThunk<any>, Definition>
7576

7677
export type BuildThunksApiEndpointMutation<
7778
Definition extends MutationDefinition<any, any, any, any, any>,
7879
> = Matchers<MutationThunk, Definition>
7980

8081
type EndpointThunk<
81-
Thunk extends QueryThunk | MutationThunk,
82+
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
8283
Definition extends EndpointDefinition<any, any, any, any>,
8384
> =
8485
Definition extends EndpointDefinition<
@@ -94,27 +95,41 @@ type EndpointThunk<
9495
ATConfig & { rejectValue: BaseQueryError<BaseQueryFn> }
9596
>
9697
: never
97-
: never
98+
: Definition extends InfiniteQueryDefinition<
99+
infer QueryArg,
100+
infer PageParam,
101+
infer BaseQueryFn,
102+
any,
103+
infer ResultType
104+
>
105+
? Thunk extends AsyncThunk<unknown, infer ATArg, infer ATConfig>
106+
? AsyncThunk<
107+
InfiniteData<ResultType, PageParam>,
108+
ATArg & { originalArgs: QueryArg },
109+
ATConfig & { rejectValue: BaseQueryError<BaseQueryFn> }
110+
>
111+
: never
112+
: never
98113

99114
export type PendingAction<
100-
Thunk extends QueryThunk | MutationThunk,
115+
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
101116
Definition extends EndpointDefinition<any, any, any, any>,
102117
> = ReturnType<EndpointThunk<Thunk, Definition>['pending']>
103118

104119
export type FulfilledAction<
105-
Thunk extends QueryThunk | MutationThunk,
120+
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
106121
Definition extends EndpointDefinition<any, any, any, any>,
107122
> = ReturnType<EndpointThunk<Thunk, Definition>['fulfilled']>
108123

109124
export type RejectedAction<
110-
Thunk extends QueryThunk | MutationThunk,
125+
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
111126
Definition extends EndpointDefinition<any, any, any, any>,
112127
> = ReturnType<EndpointThunk<Thunk, Definition>['rejected']>
113128

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

116131
export interface Matchers<
117-
Thunk extends QueryThunk | MutationThunk,
132+
Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>,
118133
Definition extends EndpointDefinition<any, any, any, any>,
119134
> {
120135
matchPending: Matcher<PendingAction<Thunk, Definition>>
@@ -645,7 +660,6 @@ export function buildThunks<
645660
isForcedQueryNeedingRefetch || !cachedData ? blankData : cachedData
646661
) as InfiniteData<unknown, unknown>
647662

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

961975
export function calculateProvidedByThunk(
962976
action: UnwrapPromise<
963-
ReturnType<ReturnType<QueryThunk>> | ReturnType<ReturnType<MutationThunk>>
977+
| ReturnType<ReturnType<QueryThunk>>
978+
| ReturnType<ReturnType<MutationThunk>>
979+
| ReturnType<ReturnType<InfiniteQueryThunk<any>>>
964980
>,
965981
type: 'providesTags' | 'invalidatesTags',
966982
endpointDefinitions: EndpointDefinitions,
967983
assertTagType: AssertTagTypes,
968984
) {
969985
return calculateProvidedBy(
970-
endpointDefinitions[action.meta.arg.endpointName][type],
986+
endpointDefinitions[action.meta.arg.endpointName][
987+
type
988+
] as ResultDescription<any, any, any, any, any>,
971989
isFulfilled(action) ? action.payload : undefined,
972990
isRejectedWithValue(action) ? action.payload : undefined,
973991
action.meta.arg.originalArgs,

packages/toolkit/src/query/endpointDefinitions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ export interface InfiniteQueryExtraOptions<
595595

596596
providesTags?: ResultDescription<
597597
TagTypes,
598-
ResultType,
598+
InfiniteData<ResultType, PageParam>,
599599
QueryArg,
600600
BaseQueryError<BaseQuery>,
601601
BaseQueryMeta<BaseQuery>

packages/toolkit/src/query/react/buildHooks.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,7 @@ export type UseInfiniteQuery<
940940
export type UseInfiniteQueryState<
941941
D extends InfiniteQueryDefinition<any, any, any, any, any>,
942942
> = <R extends Record<string, any> = UseInfiniteQueryStateDefaultResult<D>>(
943-
arg: QueryArgFrom<D> | SkipToken,
943+
arg: InfiniteQueryArgFrom<D> | SkipToken,
944944
options?: UseInfiniteQueryStateOptions<D, R>,
945945
) => UseInfiniteQueryStateResult<D, R>
946946

@@ -977,7 +977,7 @@ export type TypedUseInfiniteQueryState<
977977
export type UseInfiniteQuerySubscription<
978978
D extends InfiniteQueryDefinition<any, any, any, any, any>,
979979
> = (
980-
arg: QueryArgFrom<D> | SkipToken,
980+
arg: InfiniteQueryArgFrom<D> | SkipToken,
981981
options?: UseInfiniteQuerySubscriptionOptions<D>,
982982
) => UseInfiniteQuerySubscriptionResult<D>
983983

packages/toolkit/src/query/tests/infiniteQueries.test-d.ts

+37
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
QueryStatus,
66
} from '@reduxjs/toolkit/query/react'
77
import { setupApiStore } from '../../tests/utils/helpers'
8+
import { createSlice } from '@internal/createSlice'
89

910
describe('Infinite queries', () => {
1011
test('Basic infinite query behavior', async () => {
@@ -54,6 +55,12 @@ describe('Infinite queries', () => {
5455
InfiniteData<Pokemon[], number>
5556
>()
5657
},
58+
providesTags: (result) => {
59+
expectTypeOf(result).toEqualTypeOf<
60+
InfiniteData<Pokemon[], number> | undefined
61+
>()
62+
return []
63+
},
5764
}),
5865
}),
5966
})
@@ -68,6 +75,36 @@ describe('Infinite queries', () => {
6875

6976
expectTypeOf(pokemonApi.useGetInfinitePokemonInfiniteQuery).toBeFunction()
7077

78+
expectTypeOf(pokemonApi.endpoints.getInfinitePokemon.useInfiniteQuery)
79+
.parameter(0)
80+
.toEqualTypeOf<string | typeof skipToken>()
81+
82+
expectTypeOf(pokemonApi.endpoints.getInfinitePokemon.useInfiniteQueryState)
83+
.parameter(0)
84+
.toEqualTypeOf<string | typeof skipToken>()
85+
86+
expectTypeOf(
87+
pokemonApi.endpoints.getInfinitePokemon.useInfiniteQuerySubscription,
88+
)
89+
.parameter(0)
90+
.toEqualTypeOf<string | typeof skipToken>()
91+
92+
const slice = createSlice({
93+
name: 'pokemon',
94+
initialState: {} as { data: Pokemon[] },
95+
reducers: {},
96+
extraReducers: (builder) => {
97+
builder.addMatcher(
98+
pokemonApi.endpoints.getInfinitePokemon.matchFulfilled,
99+
(state, action) => {
100+
expectTypeOf(action.payload).toEqualTypeOf<
101+
InfiniteData<Pokemon[], number>
102+
>()
103+
},
104+
)
105+
},
106+
})
107+
71108
const res = storeRef.store.dispatch(
72109
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
73110
)

0 commit comments

Comments
 (0)