Skip to content
27 changes: 18 additions & 9 deletions src/core/infiniteQueryBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
QueryOptions,
RefetchQueryFilters,
} from './types'
import { getAbortController } from './utils'

export function infiniteQueryBehavior<
TQueryFnData,
Expand All @@ -24,11 +23,25 @@ export function infiniteQueryBehavior<
const isFetchingPreviousPage = fetchMore?.direction === 'backward'
const oldPages = context.state.data?.pages || []
const oldPageParams = context.state.data?.pageParams || []
const abortController = getAbortController()
const abortSignal = abortController?.signal
let newPageParams = oldPageParams
let cancelled = false

const addSignalProperty = (object: unknown) => {
Object.defineProperty(object, 'signal', {
enumerable: true,
get: () => {
if (context.signal?.aborted) {
cancelled = true
} else {
context.signal?.addEventListener('abort', () => {
cancelled = true
})
}
return context.signal
},
})
}

// Get query function
const queryFn =
context.options.queryFn || (() => Promise.reject('Missing queryFn'))
Expand Down Expand Up @@ -62,11 +75,12 @@ export function infiniteQueryBehavior<

const queryFnContext: QueryFunctionContext = {
queryKey: context.queryKey,
signal: abortSignal,
pageParam: param,
meta: context.meta,
}

addSignalProperty(queryFnContext)

const queryFnResult = queryFn(queryFnContext)

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

context.signal?.addEventListener('abort', () => {
cancelled = true
abortController?.abort()
})

return finalPromise
}
},
Expand Down
40 changes: 24 additions & 16 deletions src/reactjs/tests/useInfiniteQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1033,29 +1033,28 @@ describe('useInfiniteQuery', () => {
})
})

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

const initialData = { pages: [1, 2, 3, 4], pageParams: [1, 2, 3, 4] }
const initialData = { pages: [1, 2, 3, 4], pageParams: [0, 1, 2, 3] }

function List() {
const state = useInfiniteQuery(
useInfiniteQuery(
key,
async ({ pageParam }) => {
async ({ pageParam = 0, signal: _ }) => {
fetches++
await sleep(50)
return Number(pageParam)
return Number(pageParam) * 10
},
{
initialData,
getNextPageParam: lastPage => lastPage + 1,
getNextPageParam: (_, allPages) => {
return allPages.length === 4 ? undefined : allPages.length
},
}
)

states.push(state)

return null
}

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

await sleep(300)

expect(states.length).toBe(1)
expect(fetches).toBe(2)
expect(queryClient.getQueryState(key)).toMatchObject({
data: initialData,
status: 'success',
error: null,
})
if (typeof AbortSignal === 'function') {
expect(fetches).toBe(2)
expect(queryClient.getQueryState(key)).toMatchObject({
data: initialData,
status: 'success',
error: null,
})
} else {
// if AbortSignal is not consumed, fetches should not abort
expect(fetches).toBe(4)
expect(queryClient.getQueryState(key)).toMatchObject({
data: { pages: [0, 10, 20, 30], pageParams: [0, 1, 2, 3] },
status: 'success',
error: null,
})
}
})

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