Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(infiniteQuery): do not consume AbortSignal unless user has consumed it #3507

Merged
merged 8 commits into from
May 29, 2022
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