Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ stats-hydration.json
stats-react.json
stats.html
.vscode/settings.json
.idea/
4 changes: 4 additions & 0 deletions docs/src/pages/reference/useInfiniteQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ The returned properties for `useInfiniteQuery` are identical to the [`useQuery`
- `fetchNextPage: (options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult>`
- This function allows you to fetch the next "page" of results.
- `options.pageParam: unknown` allows you to manually specify a page param instead of using `getNextPageParam`.
- `options.cancelRefetch: boolean` if set to `true`, calling `fetchNextPage` repeatedly will invoke `fetchPage` every time, whether the previous
invocation has resolved or not. Also, the result from previous invocations will be ignored. If set to `false`, calling `fetchNextPage`
repeatedly won't have any effect until the first invocation has resolved. Default is `true`.
- `fetchPreviousPage: (options?: FetchPreviousPageOptions) => Promise<UseInfiniteQueryResult>`
- This function allows you to fetch the previous "page" of results.
- `options.pageParam: unknown` allows you to manually specify a page param instead of using `getPreviousPageParam`.
- `options.cancelRefetch: boolean` same as for `fetchNextPage`.
- `hasNextPage: boolean`
- This will be `true` if there is a next page to be fetched (known via the `getNextPageParam` option).
- `hasPreviousPage: boolean`
Expand Down
6 changes: 4 additions & 2 deletions src/core/infiniteQueryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ export class InfiniteQueryObserver<
options?: FetchNextPageOptions
): Promise<InfiniteQueryObserverResult<TData, TError>> {
return this.fetch({
cancelRefetch: true,
// TODO consider removing `?? true` in future breaking change, to be consistent with `refetch` API (see https://github.com/tannerlinsley/react-query/issues/2617)
cancelRefetch: options?.cancelRefetch ?? true,
throwOnError: options?.throwOnError,
meta: {
fetchMore: { direction: 'forward', pageParam: options?.pageParam },
Expand All @@ -106,7 +107,8 @@ export class InfiniteQueryObserver<
options?: FetchPreviousPageOptions
): Promise<InfiniteQueryObserverResult<TData, TError>> {
return this.fetch({
cancelRefetch: true,
// TODO consider removing `?? true` in future breaking change, to be consistent with `refetch` API (see https://github.com/tannerlinsley/react-query/issues/2617)
cancelRefetch: options?.cancelRefetch ?? true,
throwOnError: options?.throwOnError,
meta: {
fetchMore: { direction: 'backward', pageParam: options?.pageParam },
Expand Down
2 changes: 2 additions & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,12 @@ export interface ResetOptions {
}

export interface FetchNextPageOptions extends ResultOptions {
cancelRefetch?: boolean
pageParam?: unknown
}

export interface FetchPreviousPageOptions extends ResultOptions {
cancelRefetch?: boolean
pageParam?: unknown
}

Expand Down
84 changes: 84 additions & 0 deletions src/react/tests/useInfiniteQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,90 @@ describe('useInfiniteQuery', () => {
})
})

it('should silently cancel an ongoing fetchNextPage request when another fetchNextPage is invoked', async () => {
const key = queryKey()
const start = 10
const fetchPage = jest.fn(async ({ pageParam = start }) => {
await sleep(50)
return Number(pageParam)
})

function Page() {
const { fetchNextPage } = useInfiniteQuery(key, fetchPage, {
getNextPageParam: lastPage => lastPage + 1,
})

React.useEffect(() => {
setActTimeout(() => {
fetchNextPage()
}, 100)
setActTimeout(() => {
fetchNextPage()
}, 110)
}, [fetchNextPage])

return null
}

renderWithClient(queryClient, <Page />)

await sleep(300)

expect(fetchPage).toBeCalledTimes(3)
expect(fetchPage).toHaveBeenNthCalledWith(1, {
pageParam: undefined,
queryKey: [key],
})
expect(fetchPage).toHaveBeenNthCalledWith(2, {
pageParam: 11,
queryKey: [key],
})
expect(fetchPage).toHaveBeenNthCalledWith(3, {
pageParam: 11,
queryKey: [key],
})
})

it('should not cancel an ongoing fetchNextPage request when another fetchNextPage is invoked if `cancelRefetch: false` is used ', async () => {
const key = queryKey()
const start = 10
const fetchPage = jest.fn(async ({ pageParam = start }) => {
await sleep(50)
return Number(pageParam)
})

function Page() {
const { fetchNextPage } = useInfiniteQuery(key, fetchPage, {
getNextPageParam: lastPage => lastPage + 1,
})

React.useEffect(() => {
setActTimeout(() => {
fetchNextPage()
}, 100)
setActTimeout(() => {
fetchNextPage({ cancelRefetch: false })
}, 110)
}, [fetchNextPage])

return null
}

renderWithClient(queryClient, <Page />)

await sleep(300)

expect(fetchPage).toBeCalledTimes(2)
expect(fetchPage).toHaveBeenNthCalledWith(1, {
pageParam: undefined,
queryKey: [key],
})
expect(fetchPage).toHaveBeenNthCalledWith(2, {
pageParam: 11,
queryKey: [key],
})
})

it('should keep fetching first page when not loaded yet and triggering fetch more', async () => {
const key = queryKey()
const states: UseInfiniteQueryResult<number>[] = []
Expand Down