Skip to content

Commit 79f6ff8

Browse files
committed
Implement infinite query status flags (#4771)
* Extract InfiniteQueryDirection * Export page param functions * Fix useRefs with React 19 * Fix infinite query selector arg type * Implement infinite query status flags * Fix types and flags for infinite query hooks * Add new error messages
1 parent 6a971b8 commit 79f6ff8

File tree

9 files changed

+319
-131
lines changed

9 files changed

+319
-131
lines changed

errors.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@
3838
"36": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.",
3939
"37": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!",
4040
"38": "Cannot refetch a query that has not been started yet.",
41-
"39": "called \\`injectEndpoints\\` to override already-existing endpointName without specifying \\`overrideExisting: true\\`"
41+
"39": "called \\`injectEndpoints\\` to override already-existing endpointName without specifying \\`overrideExisting: true\\`",
42+
"40": "maxPages for endpoint '' must be a number greater than 0",
43+
"41": "getPreviousPageParam for endpoint '' must be a function if maxPages is used"
4244
}

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

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,6 @@ type BaseQuerySubState<
201201
* Time that the latest query was fulfilled
202202
*/
203203
fulfilledTimeStamp?: number
204-
/**
205-
* Infinite Query Specific substate properties
206-
*/
207-
hasNextPage?: boolean
208-
hasPreviousPage?: boolean
209-
direction?: 'forward' | 'backward'
210-
param?: QueryArgFrom<D>
211204
}
212205

213206
export type QuerySubState<
@@ -238,18 +231,14 @@ export type QuerySubState<
238231
}
239232
>
240233

234+
export type InfiniteQueryDirection = 'forward' | 'backward'
235+
241236
export type InfiniteQuerySubState<
242237
D extends BaseEndpointDefinition<any, any, any>,
243238
> =
244239
D extends InfiniteQueryDefinition<any, any, any, any, any>
245240
? QuerySubState<D, InfiniteData<ResultTypeFrom<D>, PageParamFrom<D>>> & {
246-
// TODO: These shouldn't be optional
247-
hasNextPage?: boolean
248-
hasPreviousPage?: boolean
249-
isFetchingNextPage?: boolean
250-
isFetchingPreviousPage?: boolean
251-
param?: PageParamFrom<D>
252-
direction?: 'forward' | 'backward'
241+
direction?: InfiniteQueryDirection
253242
}
254243
: never
255244

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { countObjectKeys, getOrInsert, isNotNullish } from '../utils'
2424
import type {
2525
InfiniteData,
2626
InfiniteQueryConfigOptions,
27+
InfiniteQueryDirection,
2728
SubscriptionOptions,
2829
} from './apiState'
2930
import type {
@@ -73,7 +74,7 @@ export type StartInfiniteQueryActionCreatorOptions<
7374
subscribe?: boolean
7475
forceRefetch?: boolean | number
7576
subscriptionOptions?: SubscriptionOptions
76-
direction?: 'forward' | 'backward'
77+
direction?: InfiniteQueryDirection
7778
[forceQueryFnSymbol]?: () => QueryReturnValue
7879
param?: unknown
7980
previous?: boolean

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

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
22
import type {
33
EndpointDefinitions,
4+
InfiniteQueryArgFrom,
45
InfiniteQueryDefinition,
56
MutationDefinition,
67
QueryArgFrom,
@@ -12,6 +13,8 @@ import type {
1213
import { expandTagDescription } from '../endpointDefinitions'
1314
import { flatten, isNotNullish } from '../utils'
1415
import type {
16+
InfiniteData,
17+
InfiniteQueryConfigOptions,
1518
InfiniteQuerySubState,
1619
MutationSubState,
1720
QueryCacheKey,
@@ -25,6 +28,7 @@ import { QueryStatus, getRequestStatusFlags } from './apiState'
2528
import { getMutationCacheKey } from './buildSlice'
2629
import type { createSelector as _createSelector } from './rtkImports'
2730
import { createNextState } from './rtkImports'
31+
import { getNextPageParam, getPreviousPageParam } from './buildThunks'
2832

2933
export type SkipToken = typeof skipToken
3034
/**
@@ -108,12 +112,23 @@ type InfiniteQueryResultSelectorFactory<
108112
Definition extends InfiniteQueryDefinition<any, any, any, any, any>,
109113
RootState,
110114
> = (
111-
queryArg: QueryArgFrom<Definition> | SkipToken,
115+
queryArg: InfiniteQueryArgFrom<Definition> | SkipToken,
112116
) => (state: RootState) => InfiniteQueryResultSelectorResult<Definition>
113117

118+
export type InfiniteQueryResultFlags = {
119+
hasNextPage: boolean
120+
hasPreviousPage: boolean
121+
isFetchingNextPage: boolean
122+
isFetchingPreviousPage: boolean
123+
isFetchNextPageError: boolean
124+
isFetchPreviousPageError: boolean
125+
}
126+
114127
export type InfiniteQueryResultSelectorResult<
115128
Definition extends InfiniteQueryDefinition<any, any, any, any, any>,
116-
> = InfiniteQuerySubState<Definition> & RequestStatusFlags
129+
> = InfiniteQuerySubState<Definition> &
130+
RequestStatusFlags &
131+
InfiniteQueryResultFlags
117132

118133
type MutationResultSelectorFactory<
119134
Definition extends MutationDefinition<any, any, any, any>,
@@ -230,7 +245,52 @@ export function buildSelectors<
230245
const finalSelectQuerySubState =
231246
queryArgs === skipToken ? selectSkippedQuery : selectQuerySubstate
232247

233-
return createSelector(finalSelectQuerySubState, withRequestFlags)
248+
const { infiniteQueryOptions } = endpointDefinition
249+
250+
function withInfiniteQueryResultFlags<T extends { status: QueryStatus }>(
251+
substate: T,
252+
): T & RequestStatusFlags & InfiniteQueryResultFlags {
253+
const infiniteSubstate = substate as InfiniteQuerySubState<any>
254+
const fetchDirection = infiniteSubstate.direction
255+
const stateWithRequestFlags = {
256+
...infiniteSubstate,
257+
...getRequestStatusFlags(substate.status),
258+
}
259+
260+
const { isLoading, isError } = stateWithRequestFlags
261+
262+
const isFetchNextPageError = isError && fetchDirection === 'forward'
263+
const isFetchingNextPage = isLoading && fetchDirection === 'forward'
264+
265+
const isFetchPreviousPageError =
266+
isError && fetchDirection === 'backward'
267+
const isFetchingPreviousPage =
268+
isLoading && fetchDirection === 'backward'
269+
270+
const hasNextPage = getHasNextPage(
271+
infiniteQueryOptions,
272+
stateWithRequestFlags.data,
273+
)
274+
const hasPreviousPage = getHasPreviousPage(
275+
infiniteQueryOptions,
276+
stateWithRequestFlags.data,
277+
)
278+
279+
return {
280+
...stateWithRequestFlags,
281+
hasNextPage,
282+
hasPreviousPage,
283+
isFetchingNextPage,
284+
isFetchingPreviousPage,
285+
isFetchNextPageError,
286+
isFetchPreviousPageError,
287+
}
288+
}
289+
290+
return createSelector(
291+
finalSelectQuerySubState,
292+
withInfiniteQueryResultFlags,
293+
)
234294
}) as InfiniteQueryResultSelectorFactory<any, RootState>
235295
}
236296

@@ -315,4 +375,20 @@ export function buildSelectors<
315375
)
316376
.map((entry) => entry.originalArgs)
317377
}
378+
379+
function getHasNextPage(
380+
options: InfiniteQueryConfigOptions<any, any>,
381+
data?: InfiniteData<unknown, unknown>,
382+
): boolean {
383+
if (!data) return false
384+
return getNextPageParam(options, data) != null
385+
}
386+
387+
function getHasPreviousPage(
388+
options: InfiniteQueryConfigOptions<any, any>,
389+
data?: InfiniteData<unknown, unknown>,
390+
): boolean {
391+
if (!data || !options.getPreviousPageParam) return false
392+
return getPreviousPageParam(options, data) != null
393+
}
318394
}

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

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import type {
2525
ConfigState,
2626
QueryKeys,
2727
InfiniteQuerySubState,
28+
InfiniteQueryDirection,
2829
} from './apiState'
2930
import { QueryStatus } from './apiState'
3031
import type {
@@ -35,14 +36,15 @@ import type {
3536
RejectedAction,
3637
} from './buildThunks'
3738
import { calculateProvidedByThunk } from './buildThunks'
38-
import type {
39-
AssertTagTypes,
40-
DefinitionType,
41-
EndpointDefinitions,
42-
FullTagDescription,
43-
QueryArgFrom,
44-
QueryDefinition,
45-
ResultTypeFrom,
39+
import {
40+
isInfiniteQueryDefinition,
41+
type AssertTagTypes,
42+
type DefinitionType,
43+
type EndpointDefinitions,
44+
type FullTagDescription,
45+
type QueryArgFrom,
46+
type QueryDefinition,
47+
type ResultTypeFrom,
4648
} from '../endpointDefinitions'
4749
import type { Patch } from 'immer'
4850
import { isDraft } from 'immer'
@@ -205,15 +207,11 @@ export function buildSlice({
205207
}
206208
substate.startedTimeStamp = meta.startedTimeStamp
207209

208-
// TODO: Awful - fix this most likely by just moving it to its own slice that only works on InfQuery's
209-
if (
210-
'param' in substate &&
211-
'direction' in substate &&
212-
'param' in arg &&
213-
'direction' in arg
214-
) {
215-
substate.param = arg.param
216-
substate.direction = arg.direction as 'forward' | 'backward' | undefined
210+
const endpointDefinition = definitions[meta.arg.endpointName]
211+
212+
if (isInfiniteQueryDefinition(endpointDefinition) && 'direction' in arg) {
213+
;(substate as InfiniteQuerySubState<any>).direction =
214+
arg.direction as InfiniteQueryDirection
217215
}
218216
})
219217
}
@@ -223,11 +221,9 @@ export function buildSlice({
223221
meta: {
224222
arg: QueryThunkArg
225223
requestId: string
226-
// requestStatus: 'fulfilled'
227224
} & {
228225
fulfilledTimeStamp: number
229226
baseQueryMeta: unknown
230-
// RTK_autoBatch: true
231227
},
232228
payload: unknown,
233229
upserting: boolean,

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

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import type {
3939
InfiniteData,
4040
InfiniteQueryConfigOptions,
4141
QueryCacheKey,
42+
InfiniteQueryDirection,
4243
} from './apiState'
4344
import { QueryStatus } from './apiState'
4445
import type {
@@ -131,7 +132,7 @@ export type InfiniteQueryThunkArg<
131132
endpointName: string
132133
param: unknown
133134
previous?: boolean
134-
direction?: 'forward' | 'backward'
135+
direction?: InfiniteQueryDirection
135136
}
136137

137138
type MutationThunkArg = {
@@ -646,31 +647,6 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
646647
}
647648
}
648649

649-
function getNextPageParam(
650-
options: InfiniteQueryConfigOptions<unknown, unknown>,
651-
{ pages, pageParams }: InfiniteData<unknown, unknown>,
652-
): unknown | undefined {
653-
const lastIndex = pages.length - 1
654-
return options.getNextPageParam(
655-
pages[lastIndex],
656-
pages,
657-
pageParams[lastIndex],
658-
pageParams,
659-
)
660-
}
661-
662-
function getPreviousPageParam(
663-
options: InfiniteQueryConfigOptions<unknown, unknown>,
664-
{ pages, pageParams }: InfiniteData<unknown, unknown>,
665-
): unknown | undefined {
666-
return options.getPreviousPageParam?.(
667-
pages[0],
668-
pages,
669-
pageParams[0],
670-
pageParams,
671-
)
672-
}
673-
674650
function isForcedQuery(
675651
arg: QueryThunkArg,
676652
state: RootState<any, string, ReducerPath>,
@@ -892,6 +868,31 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
892868
}
893869
}
894870

871+
export function getNextPageParam(
872+
options: InfiniteQueryConfigOptions<unknown, unknown>,
873+
{ pages, pageParams }: InfiniteData<unknown, unknown>,
874+
): unknown | undefined {
875+
const lastIndex = pages.length - 1
876+
return options.getNextPageParam(
877+
pages[lastIndex],
878+
pages,
879+
pageParams[lastIndex],
880+
pageParams,
881+
)
882+
}
883+
884+
export function getPreviousPageParam(
885+
options: InfiniteQueryConfigOptions<unknown, unknown>,
886+
{ pages, pageParams }: InfiniteData<unknown, unknown>,
887+
): unknown | undefined {
888+
return options.getPreviousPageParam?.(
889+
pages[0],
890+
pages,
891+
pageParams[0],
892+
pageParams,
893+
)
894+
}
895+
895896
export function calculateProvidedByThunk(
896897
action: UnwrapPromise<
897898
ReturnType<ReturnType<QueryThunk>> | ReturnType<ReturnType<MutationThunk>>

0 commit comments

Comments
 (0)