Skip to content

Commit

Permalink
Implement infinite query status flags (#4771)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
markerikson committed Dec 28, 2024
1 parent 6a971b8 commit 79f6ff8
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 131 deletions.
4 changes: 3 additions & 1 deletion errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@
"36": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.",
"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!",
"38": "Cannot refetch a query that has not been started yet.",
"39": "called \\`injectEndpoints\\` to override already-existing endpointName without specifying \\`overrideExisting: true\\`"
"39": "called \\`injectEndpoints\\` to override already-existing endpointName without specifying \\`overrideExisting: true\\`",
"40": "maxPages for endpoint '' must be a number greater than 0",
"41": "getPreviousPageParam for endpoint '' must be a function if maxPages is used"
}
17 changes: 3 additions & 14 deletions packages/toolkit/src/query/core/apiState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,6 @@ type BaseQuerySubState<
* Time that the latest query was fulfilled
*/
fulfilledTimeStamp?: number
/**
* Infinite Query Specific substate properties
*/
hasNextPage?: boolean
hasPreviousPage?: boolean
direction?: 'forward' | 'backward'
param?: QueryArgFrom<D>
}

export type QuerySubState<
Expand Down Expand Up @@ -238,18 +231,14 @@ export type QuerySubState<
}
>

export type InfiniteQueryDirection = 'forward' | 'backward'

export type InfiniteQuerySubState<
D extends BaseEndpointDefinition<any, any, any>,
> =
D extends InfiniteQueryDefinition<any, any, any, any, any>
? QuerySubState<D, InfiniteData<ResultTypeFrom<D>, PageParamFrom<D>>> & {
// TODO: These shouldn't be optional
hasNextPage?: boolean
hasPreviousPage?: boolean
isFetchingNextPage?: boolean
isFetchingPreviousPage?: boolean
param?: PageParamFrom<D>
direction?: 'forward' | 'backward'
direction?: InfiniteQueryDirection
}
: never

Expand Down
3 changes: 2 additions & 1 deletion packages/toolkit/src/query/core/buildInitiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { countObjectKeys, getOrInsert, isNotNullish } from '../utils'
import type {
InfiniteData,
InfiniteQueryConfigOptions,
InfiniteQueryDirection,
SubscriptionOptions,
} from './apiState'
import type {
Expand Down Expand Up @@ -73,7 +74,7 @@ export type StartInfiniteQueryActionCreatorOptions<
subscribe?: boolean
forceRefetch?: boolean | number
subscriptionOptions?: SubscriptionOptions
direction?: 'forward' | 'backward'
direction?: InfiniteQueryDirection
[forceQueryFnSymbol]?: () => QueryReturnValue
param?: unknown
previous?: boolean
Expand Down
82 changes: 79 additions & 3 deletions packages/toolkit/src/query/core/buildSelectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
import type {
EndpointDefinitions,
InfiniteQueryArgFrom,
InfiniteQueryDefinition,
MutationDefinition,
QueryArgFrom,
Expand All @@ -12,6 +13,8 @@ import type {
import { expandTagDescription } from '../endpointDefinitions'
import { flatten, isNotNullish } from '../utils'
import type {
InfiniteData,
InfiniteQueryConfigOptions,
InfiniteQuerySubState,
MutationSubState,
QueryCacheKey,
Expand All @@ -25,6 +28,7 @@ import { QueryStatus, getRequestStatusFlags } from './apiState'
import { getMutationCacheKey } from './buildSlice'
import type { createSelector as _createSelector } from './rtkImports'
import { createNextState } from './rtkImports'
import { getNextPageParam, getPreviousPageParam } from './buildThunks'

export type SkipToken = typeof skipToken
/**
Expand Down Expand Up @@ -108,12 +112,23 @@ type InfiniteQueryResultSelectorFactory<
Definition extends InfiniteQueryDefinition<any, any, any, any, any>,
RootState,
> = (
queryArg: QueryArgFrom<Definition> | SkipToken,
queryArg: InfiniteQueryArgFrom<Definition> | SkipToken,
) => (state: RootState) => InfiniteQueryResultSelectorResult<Definition>

export type InfiniteQueryResultFlags = {
hasNextPage: boolean
hasPreviousPage: boolean
isFetchingNextPage: boolean
isFetchingPreviousPage: boolean
isFetchNextPageError: boolean
isFetchPreviousPageError: boolean
}

export type InfiniteQueryResultSelectorResult<
Definition extends InfiniteQueryDefinition<any, any, any, any, any>,
> = InfiniteQuerySubState<Definition> & RequestStatusFlags
> = InfiniteQuerySubState<Definition> &
RequestStatusFlags &
InfiniteQueryResultFlags

type MutationResultSelectorFactory<
Definition extends MutationDefinition<any, any, any, any>,
Expand Down Expand Up @@ -230,7 +245,52 @@ export function buildSelectors<
const finalSelectQuerySubState =
queryArgs === skipToken ? selectSkippedQuery : selectQuerySubstate

return createSelector(finalSelectQuerySubState, withRequestFlags)
const { infiniteQueryOptions } = endpointDefinition

function withInfiniteQueryResultFlags<T extends { status: QueryStatus }>(
substate: T,
): T & RequestStatusFlags & InfiniteQueryResultFlags {
const infiniteSubstate = substate as InfiniteQuerySubState<any>
const fetchDirection = infiniteSubstate.direction
const stateWithRequestFlags = {
...infiniteSubstate,
...getRequestStatusFlags(substate.status),
}

const { isLoading, isError } = stateWithRequestFlags

const isFetchNextPageError = isError && fetchDirection === 'forward'
const isFetchingNextPage = isLoading && fetchDirection === 'forward'

const isFetchPreviousPageError =
isError && fetchDirection === 'backward'
const isFetchingPreviousPage =
isLoading && fetchDirection === 'backward'

const hasNextPage = getHasNextPage(
infiniteQueryOptions,
stateWithRequestFlags.data,
)
const hasPreviousPage = getHasPreviousPage(
infiniteQueryOptions,
stateWithRequestFlags.data,
)

return {
...stateWithRequestFlags,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
isFetchNextPageError,
isFetchPreviousPageError,
}
}

return createSelector(
finalSelectQuerySubState,
withInfiniteQueryResultFlags,
)
}) as InfiniteQueryResultSelectorFactory<any, RootState>
}

Expand Down Expand Up @@ -315,4 +375,20 @@ export function buildSelectors<
)
.map((entry) => entry.originalArgs)
}

function getHasNextPage(
options: InfiniteQueryConfigOptions<any, any>,
data?: InfiniteData<unknown, unknown>,
): boolean {
if (!data) return false
return getNextPageParam(options, data) != null
}

function getHasPreviousPage(
options: InfiniteQueryConfigOptions<any, any>,
data?: InfiniteData<unknown, unknown>,
): boolean {
if (!data || !options.getPreviousPageParam) return false
return getPreviousPageParam(options, data) != null
}
}
34 changes: 15 additions & 19 deletions packages/toolkit/src/query/core/buildSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {
ConfigState,
QueryKeys,
InfiniteQuerySubState,
InfiniteQueryDirection,
} from './apiState'
import { QueryStatus } from './apiState'
import type {
Expand All @@ -35,14 +36,15 @@ import type {
RejectedAction,
} from './buildThunks'
import { calculateProvidedByThunk } from './buildThunks'
import type {
AssertTagTypes,
DefinitionType,
EndpointDefinitions,
FullTagDescription,
QueryArgFrom,
QueryDefinition,
ResultTypeFrom,
import {
isInfiniteQueryDefinition,
type AssertTagTypes,
type DefinitionType,
type EndpointDefinitions,
type FullTagDescription,
type QueryArgFrom,
type QueryDefinition,
type ResultTypeFrom,
} from '../endpointDefinitions'
import type { Patch } from 'immer'
import { isDraft } from 'immer'
Expand Down Expand Up @@ -205,15 +207,11 @@ export function buildSlice({
}
substate.startedTimeStamp = meta.startedTimeStamp

// TODO: Awful - fix this most likely by just moving it to its own slice that only works on InfQuery's
if (
'param' in substate &&
'direction' in substate &&
'param' in arg &&
'direction' in arg
) {
substate.param = arg.param
substate.direction = arg.direction as 'forward' | 'backward' | undefined
const endpointDefinition = definitions[meta.arg.endpointName]

if (isInfiniteQueryDefinition(endpointDefinition) && 'direction' in arg) {
;(substate as InfiniteQuerySubState<any>).direction =
arg.direction as InfiniteQueryDirection
}
})
}
Expand All @@ -223,11 +221,9 @@ export function buildSlice({
meta: {
arg: QueryThunkArg
requestId: string
// requestStatus: 'fulfilled'
} & {
fulfilledTimeStamp: number
baseQueryMeta: unknown
// RTK_autoBatch: true
},
payload: unknown,
upserting: boolean,
Expand Down
53 changes: 27 additions & 26 deletions packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type {
InfiniteData,
InfiniteQueryConfigOptions,
QueryCacheKey,
InfiniteQueryDirection,
} from './apiState'
import { QueryStatus } from './apiState'
import type {
Expand Down Expand Up @@ -131,7 +132,7 @@ export type InfiniteQueryThunkArg<
endpointName: string
param: unknown
previous?: boolean
direction?: 'forward' | 'backward'
direction?: InfiniteQueryDirection
}

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

function getNextPageParam(
options: InfiniteQueryConfigOptions<unknown, unknown>,
{ pages, pageParams }: InfiniteData<unknown, unknown>,
): unknown | undefined {
const lastIndex = pages.length - 1
return options.getNextPageParam(
pages[lastIndex],
pages,
pageParams[lastIndex],
pageParams,
)
}

function getPreviousPageParam(
options: InfiniteQueryConfigOptions<unknown, unknown>,
{ pages, pageParams }: InfiniteData<unknown, unknown>,
): unknown | undefined {
return options.getPreviousPageParam?.(
pages[0],
pages,
pageParams[0],
pageParams,
)
}

function isForcedQuery(
arg: QueryThunkArg,
state: RootState<any, string, ReducerPath>,
Expand Down Expand Up @@ -892,6 +868,31 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
}
}

export function getNextPageParam(
options: InfiniteQueryConfigOptions<unknown, unknown>,
{ pages, pageParams }: InfiniteData<unknown, unknown>,
): unknown | undefined {
const lastIndex = pages.length - 1
return options.getNextPageParam(
pages[lastIndex],
pages,
pageParams[lastIndex],
pageParams,
)
}

export function getPreviousPageParam(
options: InfiniteQueryConfigOptions<unknown, unknown>,
{ pages, pageParams }: InfiniteData<unknown, unknown>,
): unknown | undefined {
return options.getPreviousPageParam?.(
pages[0],
pages,
pageParams[0],
pageParams,
)
}

export function calculateProvidedByThunk(
action: UnwrapPromise<
ReturnType<ReturnType<QueryThunk>> | ReturnType<ReturnType<MutationThunk>>
Expand Down
Loading

0 comments on commit 79f6ff8

Please sign in to comment.