Skip to content

Commit

Permalink
feat(query-core): improve useInfiniteQuery error handling (#7418)
Browse files Browse the repository at this point in the history
Co-authored-by: Dominik Dorfmeister <office@dorfmeister.cc>
  • Loading branch information
jasongerbes and TkDodo committed May 12, 2024
1 parent 7499ba8 commit 8ce6642
Show file tree
Hide file tree
Showing 6 changed files with 693 additions and 14 deletions.
17 changes: 11 additions & 6 deletions docs/framework/react/reference/useInfiniteQuery.md
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`.
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
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
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

0 comments on commit 8ce6642

Please sign in to comment.