diff --git a/src/core/infiniteQueryBehavior.ts b/src/core/infiniteQueryBehavior.ts index 6bca697850d..beed03d6299 100644 --- a/src/core/infiniteQueryBehavior.ts +++ b/src/core/infiniteQueryBehavior.ts @@ -17,6 +17,7 @@ export function infiniteQueryBehavior< const oldPages = context.state.data?.pages || [] const oldPageParams = context.state.data?.pageParams || [] let newPageParams = oldPageParams + let cancelled = false // Get query function const queryFn = @@ -29,6 +30,10 @@ export function infiniteQueryBehavior< param?: unknown, previous?: boolean ): Promise => { + if (cancelled) { + return Promise.reject('Cancelled') + } + if (typeof param === 'undefined' && !manual && pages.length) { return Promise.resolve(pages) } @@ -38,11 +43,7 @@ export function infiniteQueryBehavior< pageParam: param, } - let cancelFn: undefined | (() => any) const queryFnResult = queryFn(queryFnContext) - if ((queryFnResult as any).cancel) { - cancelFn = (queryFnResult as any).cancel - } const promise = Promise.resolve(queryFnResult).then(page => { newPageParams = previous @@ -51,15 +52,15 @@ export function infiniteQueryBehavior< return previous ? [page, ...pages] : [...pages, page] }) - if (cancelFn) { + if (isCancelable(queryFnResult)) { const promiseAsAny = promise as any - promiseAsAny.cancel = cancelFn + promiseAsAny.cancel = queryFnResult.cancel } return promise } - let promise + let promise: Promise // Fetch first page? if (!oldPages.length) { @@ -109,9 +110,13 @@ export function infiniteQueryBehavior< pageParams: newPageParams, })) - if (isCancelable(promise)) { - const finalPromiseAsAny = finalPromise as any - finalPromiseAsAny.cancel = promise.cancel + const finalPromiseAsAny = finalPromise as any + + finalPromiseAsAny.cancel = () => { + cancelled = true + if (isCancelable(promise)) { + promise.cancel() + } } return finalPromise diff --git a/src/react/tests/useInfiniteQuery.test.tsx b/src/react/tests/useInfiniteQuery.test.tsx index 246fe51f492..e22967bd71a 100644 --- a/src/react/tests/useInfiniteQuery.test.tsx +++ b/src/react/tests/useInfiniteQuery.test.tsx @@ -15,6 +15,7 @@ import { QueryClient, QueryCache, } from '../..' +import { CancelledError } from '../../core' interface Result { items: number[] @@ -720,6 +721,54 @@ describe('useInfiniteQuery', () => { }) }) + it('should stop fetching additional pages when the component is unmounted', async () => { + const key = queryKey() + const states: UseInfiniteQueryResult[] = [] + let fetches = 0 + + function List() { + const state = useInfiniteQuery( + key, + async ({ pageParam }) => { + fetches++ + await sleep(50) + return Number(pageParam) + }, + { + initialData: { pages: [1, 2, 3, 4], pageParams: [1, 2, 3, 4] }, + getNextPageParam: lastPage => lastPage + 1, + } + ) + + states.push(state) + + return null + } + + function Page() { + const [show, setShow] = React.useState(true) + + React.useEffect(() => { + setActTimeout(() => { + setShow(false) + }, 75) + }, []) + + return show ? : null + } + + renderWithClient(queryClient, ) + + await sleep(300) + + expect(states.length).toBe(1) + expect(fetches).toBe(2) + expect(queryClient.getQueryState(key)).toMatchObject({ + status: 'error', + error: expect.any(CancelledError), + }) + }) + it('should be able to override the cursor in the fetchNextPage callback', async () => { const key = queryKey() const states: UseInfiniteQueryResult[] = []