Skip to content

Commit 01a81ee

Browse files
committed
feat: imperative infinite queries
let's bring back imperative infinite queries, but only allow them in an all-or-nothing mode, dependent on if `getNextPageParam` has been passed
1 parent b184cd5 commit 01a81ee

File tree

5 files changed

+127
-17
lines changed

5 files changed

+127
-17
lines changed

packages/query-core/src/__tests__/infiniteQueryBehavior.test.tsx

+104
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,110 @@ describe('InfiniteQueryBehavior', () => {
201201
unsubscribe()
202202
})
203203

204+
test('InfiniteQueryBehavior should apply pageParam', async () => {
205+
const key = queryKey()
206+
207+
const queryFn = vi.fn().mockImplementation(({ pageParam }) => {
208+
return pageParam
209+
})
210+
211+
const observer = new InfiniteQueryObserver<number>(queryClient, {
212+
queryKey: key,
213+
queryFn,
214+
initialPageParam: 0,
215+
})
216+
217+
let observerResult:
218+
| InfiniteQueryObserverResult<unknown, unknown>
219+
| undefined
220+
221+
const unsubscribe = observer.subscribe((result) => {
222+
observerResult = result
223+
})
224+
225+
// Wait for the first page to be fetched
226+
await waitFor(() =>
227+
expect(observerResult).toMatchObject({
228+
isFetching: false,
229+
data: { pages: [0], pageParams: [0] },
230+
}),
231+
)
232+
233+
queryFn.mockClear()
234+
235+
// Fetch the next page using pageParam
236+
await observer.fetchNextPage({ pageParam: 1 })
237+
238+
expect(queryFn).toHaveBeenNthCalledWith(1, {
239+
queryKey: key,
240+
pageParam: 1,
241+
meta: undefined,
242+
client: queryClient,
243+
direction: 'forward',
244+
signal: expect.anything(),
245+
})
246+
247+
expect(observerResult).toMatchObject({
248+
isFetching: false,
249+
data: { pages: [0, 1], pageParams: [0, 1] },
250+
})
251+
252+
queryFn.mockClear()
253+
254+
// Fetch the previous page using pageParam
255+
await observer.fetchPreviousPage({ pageParam: -1 })
256+
257+
expect(queryFn).toHaveBeenNthCalledWith(1, {
258+
queryKey: key,
259+
pageParam: -1,
260+
meta: undefined,
261+
client: queryClient,
262+
direction: 'backward',
263+
signal: expect.anything(),
264+
})
265+
266+
expect(observerResult).toMatchObject({
267+
isFetching: false,
268+
data: { pages: [-1, 0, 1], pageParams: [-1, 0, 1] },
269+
})
270+
271+
queryFn.mockClear()
272+
273+
// Refetch pages: old manual page params should be used
274+
await observer.refetch()
275+
276+
expect(queryFn).toHaveBeenCalledTimes(3)
277+
278+
expect(queryFn).toHaveBeenNthCalledWith(1, {
279+
queryKey: key,
280+
pageParam: -1,
281+
meta: undefined,
282+
client: queryClient,
283+
direction: 'forward',
284+
signal: expect.anything(),
285+
})
286+
287+
expect(queryFn).toHaveBeenNthCalledWith(2, {
288+
queryKey: key,
289+
pageParam: 0,
290+
meta: undefined,
291+
client: queryClient,
292+
direction: 'forward',
293+
signal: expect.anything(),
294+
})
295+
296+
expect(queryFn).toHaveBeenNthCalledWith(3, {
297+
queryKey: key,
298+
pageParam: 1,
299+
meta: undefined,
300+
client: queryClient,
301+
direction: 'forward',
302+
signal: expect.anything(),
303+
})
304+
305+
unsubscribe()
306+
})
307+
204308
test('InfiniteQueryBehavior should support query cancellation', async () => {
205309
const key = queryKey()
206310
let abortSignal: AbortSignal | null = null

packages/query-core/src/infiniteQueryBehavior.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
1414
return {
1515
onFetch: (context, query) => {
1616
const options = context.options as InfiniteQueryPageParamsOptions<TData>
17-
const direction = context.fetchOptions?.meta?.fetchMore?.direction
17+
const fetchMore = context.fetchOptions?.meta?.fetchMore
1818
const oldPages = context.state.data?.pages || []
1919
const oldPageParams = context.state.data?.pageParams || []
2020
let result: InfiniteData<unknown> = { pages: [], pageParams: [] }
@@ -81,14 +81,17 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
8181
}
8282

8383
// fetch next / previous page?
84-
if (direction && oldPages.length) {
85-
const previous = direction === 'backward'
84+
if (fetchMore && oldPages.length) {
85+
const previous = fetchMore.direction === 'backward'
8686
const pageParamFn = previous ? getPreviousPageParam : getNextPageParam
8787
const oldData = {
8888
pages: oldPages,
8989
pageParams: oldPageParams,
9090
}
91-
const param = pageParamFn(options, oldData)
91+
const param =
92+
fetchMore.pageParam === undefined
93+
? pageParamFn(options, oldData)
94+
: fetchMore.pageParam
9295

9396
result = await fetchPage(oldData, param, previous)
9497
} else {
@@ -97,8 +100,8 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
97100
// Fetch all pages
98101
do {
99102
const param =
100-
currentPage === 0
101-
? (oldPageParams[0] ?? options.initialPageParam)
103+
currentPage === 0 || !options.getNextPageParam
104+
? (oldPageParams[currentPage] ?? options.initialPageParam)
102105
: getNextPageParam(options, result)
103106
if (currentPage > 0 && param == null) {
104107
break
@@ -136,7 +139,7 @@ function getNextPageParam(
136139
): unknown | undefined {
137140
const lastIndex = pages.length - 1
138141
return pages.length > 0
139-
? options.getNextPageParam(
142+
? options.getNextPageParam?.(
140143
pages[lastIndex],
141144
pages,
142145
pageParams[lastIndex],

packages/query-core/src/infiniteQueryObserver.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -124,24 +124,27 @@ export class InfiniteQueryObserver<
124124
>
125125
}
126126

127-
fetchNextPage(
128-
options?: FetchNextPageOptions,
129-
): Promise<InfiniteQueryObserverResult<TData, TError>> {
127+
fetchNextPage({ pageParam, ...options }: FetchNextPageOptions = {}): Promise<
128+
InfiniteQueryObserverResult<TData, TError>
129+
> {
130130
return this.fetch({
131131
...options,
132132
meta: {
133-
fetchMore: { direction: 'forward' },
133+
fetchMore: { direction: 'forward', pageParam },
134134
},
135135
})
136136
}
137137

138-
fetchPreviousPage(
139-
options?: FetchPreviousPageOptions,
140-
): Promise<InfiniteQueryObserverResult<TData, TError>> {
138+
fetchPreviousPage({
139+
pageParam,
140+
...options
141+
}: FetchPreviousPageOptions = {}): Promise<
142+
InfiniteQueryObserverResult<TData, TError>
143+
> {
141144
return this.fetch({
142145
...options,
143146
meta: {
144-
fetchMore: { direction: 'backward' },
147+
fetchMore: { direction: 'backward', pageParam },
145148
},
146149
})
147150
}

packages/query-core/src/query.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export interface QueryBehavior<
8989
export type FetchDirection = 'forward' | 'backward'
9090

9191
export interface FetchMeta {
92-
fetchMore?: { direction: FetchDirection }
92+
fetchMore?: { direction: FetchDirection; pageParam?: unknown }
9393
}
9494

9595
export interface FetchOptions<TData = unknown> {

packages/query-core/src/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export interface InfiniteQueryPageParamsOptions<
271271
* This function can be set to automatically get the next cursor for infinite queries.
272272
* The result will also be used to determine the value of `hasNextPage`.
273273
*/
274-
getNextPageParam: GetNextPageParamFunction<TPageParam, TQueryFnData>
274+
getNextPageParam?: GetNextPageParamFunction<TPageParam, TQueryFnData>
275275
}
276276

277277
export type ThrowOnError<

0 commit comments

Comments
 (0)