Skip to content

feat(query-core): improve useInfiniteQuery error handling #7418

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

Merged
merged 4 commits into from
May 12, 2024
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
17 changes: 11 additions & 6 deletions docs/framework/react/reference/useInfiniteQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The options for `useInfiniteQuery` are identical to the [`useQuery` hook](../use

**Returns**

The returned properties for `useInfiniteQuery` are identical to the [`useQuery` hook](../useQuery), with the addition of the following and a small difference in `isRefetching`:
The returned properties for `useInfiniteQuery` are identical to the [`useQuery` hook](../useQuery), with the addition of the following properties and a small difference in `isRefetching` and `isRefetchError`:

- `data.pages: TData[]`
- Array containing all pages.
Expand All @@ -66,19 +66,24 @@ The returned properties for `useInfiniteQuery` are identical to the [`useQuery`
- Will be `true` while fetching the previous page with `fetchPreviousPage`.
- `fetchNextPage: (options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult>`
- This function allows you to fetch the next "page" of results.
`getNextPageParam`.
- `options.cancelRefetch: boolean` if set to `true`, calling `fetchNextPage` repeatedly will invoke `fetchPage` every time, whether the previous
- `options.cancelRefetch: boolean` if set to `true`, calling `fetchNextPage` repeatedly will invoke `queryFn` 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.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).
- Will be `true` if there is a next page to be fetched (known via the `getNextPageParam` option).
- `hasPreviousPage: boolean`
- This will be `true` if there is a previous page to be fetched (known via the `getPreviousPageParam` option).
- Will be `true` if there is a previous page to be fetched (known via the `getPreviousPageParam` option).
- `isFetchNextPageError: boolean`
- Will be `true` if the query failed while fetching the next page.
- `isFetchPreviousPageError: boolean`
- Will be `true` if the query failed while fetching the previous page.
- `isRefetching: boolean`
- Is `true` whenever a background refetch is in-flight, which _does not_ include initial `pending` or fetching of next or previous page
- Will be `true` whenever a background refetch is in-flight, which _does not_ include initial `pending` or fetching of next or previous page
- Is the same as `isFetching && !isPending && !isFetchingNextPage && !isFetchingPreviousPage`
- `isRefetchError: boolean`
- Will be `true` if the query failed while refetching a page.

Keep in mind that imperative fetch calls, such as `fetchNextPage`, may interfere with the default refetch behaviour, resulting in outdated data. Make sure to call these functions only in response to user actions, or add conditions like `hasNextPage && !isFetching`.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ describe('InfiniteQueryObserver', () => {
expectTypeOf(result.data).toEqualTypeOf<InfiniteData<string, unknown>>()
expectTypeOf(result.error).toEqualTypeOf<Error>()
expectTypeOf(result.status).toEqualTypeOf<'error'>()
expectTypeOf(result.isFetchNextPageError).toEqualTypeOf<false>()
expectTypeOf(result.isFetchPreviousPageError).toEqualTypeOf<false>()
}

if (result.isSuccess) {
Expand Down
24 changes: 16 additions & 8 deletions packages/query-core/src/infiniteQueryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
FetchNextPageOptions,
FetchPreviousPageOptions,
InfiniteData,
InfiniteQueryObserverBaseResult,
InfiniteQueryObserverOptions,
InfiniteQueryObserverResult,
QueryKey,
Expand Down Expand Up @@ -145,26 +146,33 @@ export class InfiniteQueryObserver<
>,
): InfiniteQueryObserverResult<TData, TError> {
const { state } = query
const result = super.createResult(query, options)
const parentResult = super.createResult(query, options)

const { isFetching, isRefetching } = result
const { isFetching, isRefetching, isError, isRefetchError } = parentResult
const fetchDirection = state.fetchMeta?.fetchMore?.direction

const isFetchingNextPage =
isFetching && state.fetchMeta?.fetchMore?.direction === 'forward'
const isFetchNextPageError = isError && fetchDirection === 'forward'
const isFetchingNextPage = isFetching && fetchDirection === 'forward'

const isFetchingPreviousPage =
isFetching && state.fetchMeta?.fetchMore?.direction === 'backward'
const isFetchPreviousPageError = isError && fetchDirection === 'backward'
const isFetchingPreviousPage = isFetching && fetchDirection === 'backward'

return {
...result,
const result: InfiniteQueryObserverBaseResult<TData, TError> = {
...parentResult,
fetchNextPage: this.fetchNextPage,
fetchPreviousPage: this.fetchPreviousPage,
hasNextPage: hasNextPage(options, state.data),
hasPreviousPage: hasPreviousPage(options, state.data),
isFetchNextPageError,
isFetchingNextPage,
isFetchPreviousPageError,
isFetchingPreviousPage,
isRefetchError:
isRefetchError && !isFetchNextPageError && !isFetchPreviousPageError,
isRefetching:
isRefetching && !isFetchingNextPage && !isFetchingPreviousPage,
}

return result as InfiniteQueryObserverResult<TData, TError>
}
}
59 changes: 59 additions & 0 deletions packages/query-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,13 @@ export interface ResultOptions {
}

export interface RefetchOptions extends ResultOptions {
/**
* If set to `true`, a currently running request will be cancelled before a new request is made
*
* If set to `false`, no refetch will be made if there is already a request running.
*
* Defaults to `true`.
*/
cancelRefetch?: boolean
}

Expand All @@ -486,10 +493,26 @@ export interface InvalidateOptions extends RefetchOptions {}
export interface ResetOptions extends RefetchOptions {}

export interface FetchNextPageOptions extends ResultOptions {
/**
* If set to `true`, calling `fetchNextPage` repeatedly will invoke `queryFn` 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.
*
* Defaults to `true`.
*/
cancelRefetch?: boolean
}

export interface FetchPreviousPageOptions extends ResultOptions {
/**
* If set to `true`, calling `fetchPreviousPage` repeatedly will invoke `queryFn` every time,
* whether the previous invocation has resolved or not. Also, the result from previous invocations will be ignored.
*
* If set to `false`, calling `fetchPreviousPage` repeatedly won't have any effect until the first invocation has resolved.
*
* Defaults to `true`.
*/
cancelRefetch?: boolean
}

Expand Down Expand Up @@ -712,15 +735,41 @@ export interface InfiniteQueryObserverBaseResult<
TData = unknown,
TError = DefaultError,
> extends QueryObserverBaseResult<TData, TError> {
/**
* This function allows you to fetch the next "page" of results.
*/
fetchNextPage: (
options?: FetchNextPageOptions,
) => Promise<InfiniteQueryObserverResult<TData, TError>>
/**
* This function allows you to fetch the previous "page" of results.
*/
fetchPreviousPage: (
options?: FetchPreviousPageOptions,
) => Promise<InfiniteQueryObserverResult<TData, TError>>
/**
* Will be `true` if there is a next page to be fetched (known via the `getNextPageParam` option).
*/
hasNextPage: boolean
/**
* Will be `true` if there is a previous page to be fetched (known via the `getPreviousPageParam` option).
*/
hasPreviousPage: boolean
/**
* Will be `true` if the query failed while fetching the next page.
*/
isFetchNextPageError: boolean
/**
* Will be `true` while fetching the next page with `fetchNextPage`.
*/
isFetchingNextPage: boolean
/**
* Will be `true` if the query failed while fetching the previous page.
*/
isFetchPreviousPageError: boolean
/**
* Will be `true` while fetching the previous page with `fetchPreviousPage`.
*/
isFetchingPreviousPage: boolean
}

Expand All @@ -734,6 +783,8 @@ export interface InfiniteQueryObserverPendingResult<
isPending: true
isLoadingError: false
isRefetchError: false
isFetchNextPageError: false
isFetchPreviousPageError: false
isSuccess: false
status: 'pending'
}
Expand All @@ -749,6 +800,8 @@ export interface InfiniteQueryObserverLoadingResult<
isLoading: true
isLoadingError: false
isRefetchError: false
isFetchNextPageError: false
isFetchPreviousPageError: false
isSuccess: false
status: 'pending'
}
Expand All @@ -764,6 +817,8 @@ export interface InfiniteQueryObserverLoadingErrorResult<
isLoading: false
isLoadingError: true
isRefetchError: false
isFetchNextPageError: false
isFetchPreviousPageError: false
isSuccess: false
status: 'error'
}
Expand All @@ -779,6 +834,8 @@ export interface InfiniteQueryObserverRefetchErrorResult<
isLoading: false
isLoadingError: false
isRefetchError: true
isFetchNextPageError: false
isFetchPreviousPageError: false
isSuccess: false
status: 'error'
}
Expand All @@ -794,6 +851,8 @@ export interface InfiniteQueryObserverSuccessResult<
isLoading: false
isLoadingError: false
isRefetchError: false
isFetchNextPageError: false
isFetchPreviousPageError: false
isSuccess: true
status: 'success'
}
Expand Down
Loading