Skip to content

Commit 03fa7e1

Browse files
committed
fix: retry for infinite queries
The retryer lives above the fetchFn, and it re-runs the fetchFn whenever a retry happens. Usually, the fetchFn is a thin wrapper around the actual queryFn passed by the user. However, for infinite queries, it fetches all pages in a loop. The retryer breaks out of this loop if an error occurs on e.g. the second page, and then retries by running the fetchFn - which will re-set the loop This fix hoists the currentPage counter out of the fetchFn - into the closure created by onFetch. The outer closure is created from running `query.fetch` once, so it won't be re-set between retries. The fix also re-writes the fetch loop to always take the `currentPage` into account, where it was previously treating the first page differently
1 parent 4f568ad commit 03fa7e1

File tree

2 files changed

+17
-28
lines changed

2 files changed

+17
-28
lines changed

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

+2-10
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,7 @@ describe('InfiniteQueryBehavior', () => {
329329
unsubscribe()
330330
})
331331

332-
// Ensures https://github.com/TanStack/query/issues/8046 is resolved
333-
test('InfiniteQueryBehavior should not enter an infinite loop when a page errors while retry is on', async () => {
332+
test('InfiniteQueryBehavior should not enter an infinite loop when a page errors while retry is on #8046', async () => {
334333
let errorCount = 0
335334
const key = queryKey()
336335

@@ -348,17 +347,14 @@ describe('InfiniteQueryBehavior', () => {
348347

349348
const fetchData = async ({ nextToken = 0 }: { nextToken?: number }) =>
350349
new Promise<TestResponse>((resolve, reject) => {
351-
console.log(`getting page ${nextToken}...`)
352350
setTimeout(() => {
353351
if (nextToken == 2 && errorCount < 3) {
354352
errorCount += 1
355-
console.error('rate limited!')
356353
reject({ statusCode: 429 })
357354
return
358355
}
359356
resolve(fakeData[nextToken] as TestResponse)
360-
console.log(`got page ${nextToken}`)
361-
}, 500)
357+
}, 10)
362358
})
363359

364360
const observer = new InfiniteQueryObserver<
@@ -395,13 +391,9 @@ describe('InfiniteQueryBehavior', () => {
395391
// infinite loop where the retryer every time restarts from page 1
396392
// once it reaches the page where it errors.
397393
// For this to work, we'd need to reset the error count so we actually retry
398-
console.log('###################################################')
399394
errorCount = 0
400395
const reFetchedData = await observer.refetch()
401396

402-
// Before resolution (while the bug persists), this will never be reached
403-
// and the test will timeout.
404397
expect(reFetchedData.data?.pageParams).toEqual([1, 2, 3])
405-
console.log(reFetchedData.data?.pageParams)
406398
})
407399
})

packages/query-core/src/infiniteQueryBehavior.ts

+15-18
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
1313
): QueryBehavior<TQueryFnData, TError, InfiniteData<TData, TPageParam>> {
1414
return {
1515
onFetch: (context, query) => {
16+
const options = context.options as InfiniteQueryPageParamsOptions<TData>
17+
const direction = context.fetchOptions?.meta?.fetchMore?.direction
18+
const oldPages = context.state.data?.pages || []
19+
const oldPageParams = context.state.data?.pageParams || []
20+
let result: InfiniteData<unknown> = { pages: [], pageParams: [] }
21+
let currentPage = 0
22+
1623
const fetchFn = async () => {
17-
const options = context.options as InfiniteQueryPageParamsOptions<TData>
18-
const direction = context.fetchOptions?.meta?.fetchMore?.direction
19-
const oldPages = context.state.data?.pages || []
20-
const oldPageParams = context.state.data?.pageParams || []
21-
const empty = { pages: [], pageParams: [] }
2224
let cancelled = false
23-
2425
const addSignalProperty = (object: unknown) => {
2526
Object.defineProperty(object, 'signal', {
2627
enumerable: true,
@@ -78,8 +79,6 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
7879
}
7980
}
8081

81-
let result: InfiniteData<unknown>
82-
8382
// fetch next / previous page?
8483
if (direction && oldPages.length) {
8584
const previous = direction === 'backward'
@@ -92,22 +91,20 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
9291

9392
result = await fetchPage(oldData, param, previous)
9493
} else {
95-
// Fetch first page
96-
result = await fetchPage(
97-
empty,
98-
oldPageParams[0] ?? options.initialPageParam,
99-
)
100-
10194
const remainingPages = pages ?? oldPages.length
10295

103-
// Fetch remaining pages
104-
for (let i = 1; i < remainingPages; i++) {
105-
const param = getNextPageParam(options, result)
96+
// Fetch all pages
97+
do {
98+
const param =
99+
currentPage === 0
100+
? (oldPageParams[0] ?? options.initialPageParam)
101+
: getNextPageParam(options, result)
106102
if (param == null) {
107103
break
108104
}
109105
result = await fetchPage(result, param)
110-
}
106+
currentPage++
107+
} while (currentPage < remainingPages)
111108
}
112109

113110
return result

0 commit comments

Comments
 (0)