Skip to content

Commit 4089f11

Browse files
committed
Shelf
1 parent 3dc44fd commit 4089f11

File tree

7 files changed

+203
-32
lines changed

7 files changed

+203
-32
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,15 @@ export type InfiniteQueryActionCreatorResult<
102102
arg: QueryArgFrom<D>
103103
requestId: string
104104
subscriptionOptions: SubscriptionOptions | undefined
105+
infiniteQueryOptions: InfiniteQueryConfigOptions | undefined
105106
abort(): void
106107
unwrap(): Promise<ResultTypeFrom<D>>
107108
unsubscribe(): void
108109
refetch(): QueryActionCreatorResult<D>
109110
fetchNextPage(): QueryActionCreatorResult<D>
110111
fetchPreviousPage(): QueryActionCreatorResult<D>
111112
updateSubscriptionOptions(options: SubscriptionOptions): void
113+
updateInfiniteQueryOptions(options: InfiniteQueryConfigOptions): void
112114
queryCacheKey: string
113115
}
114116

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { InternalHandlerBuilder, QueryStateMeta, TimeoutId } from '@internal/query/core/buildMiddleware/types'
2+
3+
export const buildInfiniteQueryHandler: InternalHandlerBuilder = ({
4+
reducerPath,
5+
queryThunk,
6+
api,
7+
fetchNextPage,
8+
internalState,
9+
}) => {
10+
const currentData: QueryStateMeta<{
11+
nextPollTimestamp: number
12+
timeout?: TimeoutId
13+
pollingInterval: number
14+
}> = {}
15+
16+
17+
18+
}

packages/toolkit/src/query/core/buildMiddleware/types.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ export type SubMiddlewareApi = MiddlewareAPI<
5656
RootState<EndpointDefinitions, string, string>
5757
>
5858

59+
export interface BuildInfMiddlewareInput
60+
extends BuildMiddlewareInput<EndpointDefinitions, string, string> {
61+
internalState: InternalMiddlewareState
62+
fetchNextPage(
63+
querySubState: QuerySubState<any>,
64+
queryCacheKey: string,
65+
arg: unknown,
66+
): AsyncThunkAction<ThunkResult, QueryThunkArg, {}>
67+
isThisApiSliceAction: (action: Action) => boolean
68+
}
69+
5970
export interface BuildSubMiddlewareInput
6071
extends BuildMiddlewareInput<EndpointDefinitions, string, string> {
6172
internalState: InternalMiddlewareState
@@ -70,6 +81,14 @@ export interface BuildSubMiddlewareInput
7081
isThisApiSliceAction: (action: Action) => boolean
7182
}
7283

84+
export type InfMiddlewareBuilder = (
85+
input: BuildInfMiddlewareInput,
86+
) => Middleware<
87+
{},
88+
RootState<EndpointDefinitions, string, string>,
89+
ThunkDispatch<any, any, UnknownAction>
90+
>
91+
7392
export type SubMiddlewareBuilder = (
7493
input: BuildSubMiddlewareInput,
7594
) => Middleware<
@@ -87,7 +106,7 @@ export type ApiMiddlewareInternalHandler<Return = void> = (
87106
) => Return
88107

89108
export type InternalHandlerBuilder<ReturnType = void> = (
90-
input: BuildSubMiddlewareInput,
109+
input: BuildSubMiddlewareInput | BuildInfMiddlewareInput,
91110
) => ApiMiddlewareInternalHandler<ReturnType>
92111

93112
export interface PromiseConstructorWithKnownReason {

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

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { calculateProvidedByThunk } from './buildThunks'
2828
import type {
2929
AssertTagTypes,
3030
EndpointDefinitions,
31-
FullTagDescription,
32-
QueryDefinition,
31+
FullTagDescription, InfiniteQueryDefinition,
32+
QueryDefinition
3333
} from '../endpointDefinitions'
3434
import type { Patch } from 'immer'
3535
import { isDraft } from 'immer'
@@ -426,32 +426,71 @@ export function buildSlice({
426426
name: `${reducerPath}/infinitequeries`,
427427
initialState: initialState as QueryState<any>,
428428
reducers: {
429-
changeDirection: {
430-
reducer(
431-
draft,
432-
{ payload: { queryCacheKey } }: PayloadAction<QuerySubstateIdentifier>
433-
) {
434-
},
435-
prepare: prepareAutoBatched<QuerySubstateIdentifier>(),
436-
},
437-
combineArgsFromSelection: {
438-
reducer(
439-
draft,
429+
fetchNextPage(
430+
d,
431+
a: PayloadAction<
440432
{
441-
payload: { queryCacheKey, patches },
442-
}: PayloadAction<
443-
QuerySubstateIdentifier & { patches: readonly Patch[] }
444-
>
445-
) {
446-
updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => {
447-
substate.originalArgs = substate
448-
})
449-
},
450-
prepare: prepareAutoBatched<
451-
QuerySubstateIdentifier & { patches: readonly Patch[] }
452-
>(),
433+
endpointName: string
434+
requestId: string
435+
options: Subscribers[number]
436+
} & QuerySubstateIdentifier
437+
>,
438+
) {
439+
// Dummy
453440
},
441+
unsubscribeQueryResult(
442+
d,
443+
a: PayloadAction<{ requestId: string } & QuerySubstateIdentifier>,
444+
) {
445+
// Dummy
446+
},
447+
internal_getRTKQSubscriptions() {},
454448
},
449+
// extraReducers(builder) {
450+
// builder
451+
// .addCase(queryThunk.fulfilled, (draft, { meta, payload }) => {
452+
// updateQuerySubstateIfExists(
453+
// draft,
454+
// meta.arg.queryCacheKey,
455+
// (substate) => {
456+
// const { infiniteQueryOptions } = definitions[
457+
// meta.arg.endpointName
458+
// ] as InfiniteQueryDefinition<any, any, any, any>
459+
// substate.status = QueryStatus.fulfilled
460+
// if(!infiniteQueryOptions) return
461+
//
462+
// if (substate.data !== undefined) {
463+
// const { fulfilledTimeStamp, arg, baseQueryMeta, requestId } =
464+
// meta
465+
// // There's existing cache data. Let the user merge it in themselves.
466+
// // We're already inside an Immer-powered reducer, and the user could just mutate `substate.data`
467+
// // themselves inside of `merge()`. But, they might also want to return a new value.
468+
// // Try to let Immer figure that part out, save the result, and assign it to `substate.data`.
469+
// substate.data = payload
470+
// } else {
471+
// // Presumably a fresh request. Just cache the response data.
472+
// substate.data = payload
473+
// }
474+
// } else {
475+
// // Assign or safely update the cache data.
476+
// substate.data =
477+
// definitions[meta.arg.endpointName].structuralSharing ?? true
478+
// ? copyWithStructuralSharing(
479+
// isDraft(substate.data)
480+
// ? original(substate.data)
481+
// : substate.data,
482+
// payload,
483+
// )
484+
// : payload
485+
// }
486+
//
487+
// delete substate.error
488+
// substate.fulfilledTimeStamp = meta.fulfilledTimeStamp
489+
// },
490+
// )
491+
// })
492+
493+
// },
455494
})
456495

457496

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ export function buildThunks<
444444
previous?: boolean,
445445
): Promise<QueryReturnValue> => {
446446

447+
console.log('fetchPage', data, param, previous)
448+
447449
if (param == null && data.pages.length) {
448450
return Promise.resolve({data})
449451
}
@@ -455,24 +457,24 @@ export function buildThunks<
455457
endpointDefinition.extraOptions as any
456458
)
457459

458-
const { maxPages } = endpointDefinition.extraOptions as any
460+
const maxPages = 20
459461
const addTo = previous ? addToStart : addToEnd
460462

461463
return {
462464
data: {
463-
pages: addTo(data.pages, page, maxPages),
465+
pages: addTo(data.pages, page.data, maxPages),
464466
pageParams: addTo(data.pageParams, param, maxPages),
465467
},
466468
}
467469
}
468470

469-
if ('direction' in arg && arg.infiniteQueryOptions) {
471+
if ('infiniteQueryOptions' in endpointDefinition) {
470472
if (arg.direction && arg.data.pages.length) {
471473

472474
const previous = arg.direction === 'backwards'
473475
const pageParamFn = previous ? getPreviousPageParam : getNextPageParam
474476
const oldData = arg.data
475-
const param = pageParamFn(arg.infiniteQueryOptions, oldData)
477+
const param = pageParamFn(endpointDefinition.infiniteQueryOptions, oldData)
476478

477479
result = await fetchPage(oldData, param, previous)
478480
} else {

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import type { SerializeQueryArgs } from './defaultSerializeQueryArgs'
2-
import type { QuerySubState, RootState } from './core/apiState'
2+
import type {
3+
GetNextPageParamFunction,
4+
GetPreviousPageParamFunction,
5+
InfiniteQueryConfigOptions,
6+
QuerySubState,
7+
RootState
8+
} from './core/apiState'
39
import type {
410
BaseQueryExtraOptions,
511
BaseQueryFn,
@@ -553,7 +559,7 @@ export interface InfiniteQueryExtraOptions<
553559
*/
554560
invalidatesTags?: never
555561

556-
selection: ({ from, to }: any, { read }: any) => any
562+
infiniteQueryOptions: InfiniteQueryConfigOptions
557563

558564
/**
559565
* All of these are `undefined` at runtime, purely to be used in TypeScript declarations!

packages/toolkit/src/query/tests/buildHooks.test.tsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,91 @@ describe('hooks tests', () => {
724724
expect(res.data!.amount).toBeGreaterThan(originalAmount)
725725
})
726726

727+
728+
test('Infinite Query Hook', async () => {
729+
function delay(ms: number) {
730+
return new Promise((resolve) => setTimeout(resolve, ms))
731+
}
732+
733+
function paginate<T>(array: T[], page_size: number, page_number: number) {
734+
// human-readable page numbers usually start with 1, so we reduce 1 in the first argument
735+
return array.slice((page_number - 1) * page_size, page_number * page_size)
736+
}
737+
738+
server.use(
739+
http.get('https://example.com/listItems', ({ request }) => {
740+
const url = new URL(request.url)
741+
const pageString = url.searchParams.get('page')
742+
const pageNum = parseInt(pageString || '0')
743+
744+
const results = {title: `page ${pageNum}`, info: "more name"}
745+
return HttpResponse.json(results)
746+
}),
747+
)
748+
749+
750+
const pokemonApi = createApi({
751+
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
752+
endpoints: (builder) => ({
753+
getInfinitePokemon: builder.infiniteQuery<any, number>({
754+
infiniteQueryOptions: {
755+
getNextPageParam: (lastPage) => lastPage + 1,
756+
},
757+
query(pageParam = 0) {
758+
return `https://example.com/listItems?page=${pageParam}`
759+
}
760+
}),
761+
}),
762+
})
763+
764+
765+
const storeRef = setupApiStore(pokemonApi, undefined, {
766+
withoutTestLifecycles: true,
767+
})
768+
769+
const checkNumQueries = (count: number) => {
770+
const cacheEntries = Object.keys((storeRef.store.getState()).api.queries)
771+
const queries = cacheEntries.length
772+
console.log('queries', queries)
773+
console.log(storeRef.store.getState().api.queries)
774+
775+
expect(queries).toBe(count)
776+
}
777+
778+
let i = 0;
779+
780+
function User() {
781+
const { data, isFetching, isUninitialized } =
782+
pokemonApi.useGetInfinitePokemonQuery(0)
783+
784+
return (
785+
<div>
786+
<div data-testid="isUninitialized">{String(isUninitialized)}</div>
787+
<div data-testid="isFetching">{String(isFetching)}</div>
788+
<div data-testid="data">
789+
{String(data)}
790+
</div>
791+
<button data-testid="nextPage" onClick={() => console.log(pokemonApi.endpoints?.getInfinitePokemon)}>
792+
nextPage
793+
</button>
794+
</div>
795+
)
796+
}
797+
798+
render(<User />, { wrapper: storeRef.wrapper })
799+
expect(screen.getByTestId('data').textContent).toBe('undefined')
800+
checkNumQueries(1)
801+
802+
await waitFor(() =>
803+
expect(screen.getByTestId('isUninitialized').textContent).toBe('false'),
804+
)
805+
await waitFor(() =>
806+
expect(screen.getByTestId('isFetching').textContent).toBe('false'),
807+
)
808+
fireEvent.click(screen.getByTestId('nextPage'))
809+
checkNumQueries(2)
810+
})
811+
727812
// See https://github.com/reduxjs/redux-toolkit/issues/4267 - Memory leak in useQuery rapid query arg changes
728813
test('Hook subscriptions are properly cleaned up when query is fulfilled/rejected', async () => {
729814
// This is imported already, but it seems to be causing issues with the test on certain matrixes

0 commit comments

Comments
 (0)