Skip to content

Commit

Permalink
fix: stop fetching pages when an infinite query unmounts (#1761)
Browse files Browse the repository at this point in the history
  • Loading branch information
boschni committed Feb 7, 2021
1 parent 0b8c55c commit 3d2806e
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 12 deletions.
25 changes: 15 additions & 10 deletions src/core/infiniteQueryBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -29,6 +30,10 @@ export function infiniteQueryBehavior<
param?: unknown,
previous?: boolean
): Promise<unknown[]> => {
if (cancelled) {
return Promise.reject('Cancelled')
}

if (typeof param === 'undefined' && !manual && pages.length) {
return Promise.resolve(pages)
}
Expand All @@ -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
Expand All @@ -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<unknown[]>

// Fetch first page?
if (!oldPages.length) {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/hydration/tests/ssr.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ describe('Server side rendering with de/rehydration', () => {
expect(consoleMock).toHaveBeenCalledTimes(1)
expect(fetchDataError).toHaveBeenCalledTimes(1)
expect(el.innerHTML).toBe(expectedMarkup)
await sleep(10)
await sleep(50)
expect(fetchDataError).toHaveBeenCalledTimes(2)
expect(el.innerHTML).toBe(
'ErrorComponent - status:error fetching:false data:undefined'
Expand Down Expand Up @@ -208,7 +208,7 @@ describe('Server side rendering with de/rehydration', () => {
expect(consoleMock).not.toHaveBeenCalled()
expect(fetchDataSuccess).toHaveBeenCalledTimes(0)
expect(el.innerHTML).toBe(expectedMarkup)
await sleep(10)
await sleep(50)
expect(fetchDataSuccess).toHaveBeenCalledTimes(1)
expect(el.innerHTML).toBe(
'SuccessComponent - status:success fetching:false data:success!'
Expand Down
49 changes: 49 additions & 0 deletions src/react/tests/useInfiniteQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
QueryClient,
QueryCache,
} from '../..'
import { CancelledError } from '../../core'

interface Result {
items: number[]
Expand Down Expand Up @@ -720,6 +721,54 @@ describe('useInfiniteQuery', () => {
})
})

it('should stop fetching additional pages when the component is unmounted', async () => {
const key = queryKey()
const states: UseInfiniteQueryResult<number>[] = []
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 ? <List /> : null
}

renderWithClient(queryClient, <Page />)

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<number>[] = []
Expand Down

1 comment on commit 3d2806e

@vercel
Copy link

@vercel vercel bot commented on 3d2806e Feb 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.