Skip to content

Commit 068b974

Browse files
authored
fix(infiniteQuery): do not consume AbortSignal unless user has consumed it (#3507)
* fix(infiniteQuery): do not consume AbortSignal unless user has consumed it calling context.signal?.addEventListener did consume the signal * fix(infiniteQuery): do not consume AbortSignal unless user has consumed it fix formatting * re-write test to reflect the reality we want to continue fetching pages in the background even if the infinite query unmounts, unless the abort signal has been consumed. That is the documented behaviour, and also what useQuery is doing. * fix test
1 parent 270efe6 commit 068b974

File tree

2 files changed

+42
-25
lines changed

2 files changed

+42
-25
lines changed

src/core/infiniteQueryBehavior.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
QueryOptions,
77
RefetchQueryFilters,
88
} from './types'
9-
import { getAbortController } from './utils'
109

1110
export function infiniteQueryBehavior<
1211
TQueryFnData,
@@ -24,11 +23,25 @@ export function infiniteQueryBehavior<
2423
const isFetchingPreviousPage = fetchMore?.direction === 'backward'
2524
const oldPages = context.state.data?.pages || []
2625
const oldPageParams = context.state.data?.pageParams || []
27-
const abortController = getAbortController()
28-
const abortSignal = abortController?.signal
2926
let newPageParams = oldPageParams
3027
let cancelled = false
3128

29+
const addSignalProperty = (object: unknown) => {
30+
Object.defineProperty(object, 'signal', {
31+
enumerable: true,
32+
get: () => {
33+
if (context.signal?.aborted) {
34+
cancelled = true
35+
} else {
36+
context.signal?.addEventListener('abort', () => {
37+
cancelled = true
38+
})
39+
}
40+
return context.signal
41+
},
42+
})
43+
}
44+
3245
// Get query function
3346
const queryFn =
3447
context.options.queryFn || (() => Promise.reject('Missing queryFn'))
@@ -62,11 +75,12 @@ export function infiniteQueryBehavior<
6275

6376
const queryFnContext: QueryFunctionContext = {
6477
queryKey: context.queryKey,
65-
signal: abortSignal,
6678
pageParam: param,
6779
meta: context.meta,
6880
}
6981

82+
addSignalProperty(queryFnContext)
83+
7084
const queryFnResult = queryFn(queryFnContext)
7185

7286
const promise = Promise.resolve(queryFnResult).then(page =>
@@ -143,11 +157,6 @@ export function infiniteQueryBehavior<
143157
pageParams: newPageParams,
144158
}))
145159

146-
context.signal?.addEventListener('abort', () => {
147-
cancelled = true
148-
abortController?.abort()
149-
})
150-
151160
return finalPromise
152161
}
153162
},

src/reactjs/tests/useInfiniteQuery.test.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,29 +1033,28 @@ describe('useInfiniteQuery', () => {
10331033
})
10341034
})
10351035

1036-
it('should stop fetching additional pages when the component is unmounted', async () => {
1036+
it('should stop fetching additional pages when the component is unmounted and AbortSignal is consumed', async () => {
10371037
const key = queryKey()
1038-
const states: UseInfiniteQueryResult<number>[] = []
10391038
let fetches = 0
10401039

1041-
const initialData = { pages: [1, 2, 3, 4], pageParams: [1, 2, 3, 4] }
1040+
const initialData = { pages: [1, 2, 3, 4], pageParams: [0, 1, 2, 3] }
10421041

10431042
function List() {
1044-
const state = useInfiniteQuery(
1043+
useInfiniteQuery(
10451044
key,
1046-
async ({ pageParam }) => {
1045+
async ({ pageParam = 0, signal: _ }) => {
10471046
fetches++
10481047
await sleep(50)
1049-
return Number(pageParam)
1048+
return Number(pageParam) * 10
10501049
},
10511050
{
10521051
initialData,
1053-
getNextPageParam: lastPage => lastPage + 1,
1052+
getNextPageParam: (_, allPages) => {
1053+
return allPages.length === 4 ? undefined : allPages.length
1054+
},
10541055
}
10551056
)
10561057

1057-
states.push(state)
1058-
10591058
return null
10601059
}
10611060

@@ -1075,13 +1074,22 @@ describe('useInfiniteQuery', () => {
10751074

10761075
await sleep(300)
10771076

1078-
expect(states.length).toBe(1)
1079-
expect(fetches).toBe(2)
1080-
expect(queryClient.getQueryState(key)).toMatchObject({
1081-
data: initialData,
1082-
status: 'success',
1083-
error: null,
1084-
})
1077+
if (typeof AbortSignal === 'function') {
1078+
expect(fetches).toBe(2)
1079+
expect(queryClient.getQueryState(key)).toMatchObject({
1080+
data: initialData,
1081+
status: 'success',
1082+
error: null,
1083+
})
1084+
} else {
1085+
// if AbortSignal is not consumed, fetches should not abort
1086+
expect(fetches).toBe(4)
1087+
expect(queryClient.getQueryState(key)).toMatchObject({
1088+
data: { pages: [0, 10, 20, 30], pageParams: [0, 1, 2, 3] },
1089+
status: 'success',
1090+
error: null,
1091+
})
1092+
}
10851093
})
10861094

10871095
it('should be able to override the cursor in the fetchNextPage callback', async () => {

0 commit comments

Comments
 (0)