Skip to content

Commit

Permalink
Implement infinite query status flags
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson committed Dec 15, 2024
1 parent ac4bac1 commit e885c6a
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 86 deletions.
13 changes: 0 additions & 13 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 @@ -245,12 +238,6 @@ export type InfiniteQuerySubState<
> =
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?: InfiniteQueryDirection
}
: never
Expand Down
79 changes: 77 additions & 2 deletions packages/toolkit/src/query/core/buildSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type {
import { expandTagDescription } from '../endpointDefinitions'
import { flatten, isNotNullish } from '../utils'
import type {
InfiniteData,
InfiniteQueryConfigOptions,
InfiniteQuerySubState,
MutationSubState,
QueryCacheKey,
Expand All @@ -26,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 @@ -112,9 +115,20 @@ type InfiniteQueryResultSelectorFactory<
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 @@ -231,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 @@ -316,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
57 changes: 56 additions & 1 deletion packages/toolkit/src/query/tests/buildHooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import type { SyncScreen } from '@testing-library/react-render-stream/pure'
import { createRenderStream } from '@testing-library/react-render-stream/pure'
import { HttpResponse, http } from 'msw'
import { useEffect, useState } from 'react'
import type { InfiniteQueryResultFlags } from '../core/buildSelectors'

// Just setup a temporary in-memory counter for tests that `getIncrementedAmount`.
// This can be used to test how many renders happen due to data changes or
Expand Down Expand Up @@ -1781,7 +1782,7 @@ describe('hooks tests', () => {
)
})

test('useInfiniteQuery fetchNextPage Trigger', async () => {
test.only('useInfiniteQuery fetchNextPage Trigger', async () => {
const storeRef = setupApiStore(pokemonApi, undefined, {
withoutTestLifecycles: true,
})
Expand All @@ -1798,6 +1799,26 @@ describe('hooks tests', () => {
expect(queries).toBe(count)
}

const checkEntryFlags = (
arg: string,
expectedFlags: Partial<InfiniteQueryResultFlags>,
) => {
const selector = pokemonApi.endpoints.getInfinitePokemon.select(arg)
const entry = selector(storeRef.store.getState())

const actualFlags: InfiniteQueryResultFlags = {
hasNextPage: false,
hasPreviousPage: false,
isFetchingNextPage: false,
isFetchingPreviousPage: false,
isFetchNextPageError: false,
isFetchPreviousPageError: false,
...expectedFlags,
}

expect(entry).toMatchObject(actualFlags)
}

const checkPageRows = (
withinDOM: () => SyncScreen,
type: string,
Expand Down Expand Up @@ -1836,30 +1857,64 @@ describe('hooks tests', () => {

const utils = render(<PokemonList />, { wrapper: storeRef.wrapper })
checkNumQueries(1)
checkEntryFlags('fire', {})
await waitForFetch(true)
checkNumQueries(1)
checkPageRows(getCurrentRender().withinDOM, 'fire', [0])
checkEntryFlags('fire', {
hasNextPage: true,
})

fireEvent.click(screen.getByTestId('nextPage'), {})
checkEntryFlags('fire', {
hasNextPage: true,
isFetchingNextPage: true,
})
await waitForFetch()
checkPageRows(getCurrentRender().withinDOM, 'fire', [0, 1])
checkEntryFlags('fire', {
hasNextPage: true,
})

fireEvent.click(screen.getByTestId('nextPage'))
await waitForFetch()
checkPageRows(getCurrentRender().withinDOM, 'fire', [0, 1, 2])

utils.rerender(<PokemonList arg="water" initialPageParam={3} />)
checkEntryFlags('water', {})
await waitForFetch(true)
checkNumQueries(2)
checkPageRows(getCurrentRender().withinDOM, 'water', [3])
checkEntryFlags('water', {
hasNextPage: true,
hasPreviousPage: true,
})

fireEvent.click(screen.getByTestId('nextPage'))
checkEntryFlags('water', {
hasNextPage: true,
hasPreviousPage: true,
isFetchingNextPage: true,
})
await waitForFetch()
checkPageRows(getCurrentRender().withinDOM, 'water', [3, 4])
checkEntryFlags('water', {
hasNextPage: true,
hasPreviousPage: true,
})

fireEvent.click(screen.getByTestId('prevPage'))
checkEntryFlags('water', {
hasNextPage: true,
hasPreviousPage: true,
isFetchingPreviousPage: true,
})
await waitForFetch()
checkPageRows(getCurrentRender().withinDOM, 'water', [2, 3, 4])
checkEntryFlags('water', {
hasNextPage: true,
hasPreviousPage: true,
})
})
})

Expand Down
Loading

0 comments on commit e885c6a

Please sign in to comment.