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
7 changes: 7 additions & 0 deletions docs/rtk-query/api/createApi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,13 @@ export type InfiniteQueryDefinition<
* direction will be dropped from the cache.
*/
maxPages?: number
/**
* Defaults to `true`. When this is `true` and an infinite query endpoint is refetched
* (due to tag invalidation, polling, arg change configuration, or manual refetching),
* RTK Query will try to sequentially refetch all pages currently in the cache.
* When `false` only the first page will be refetched.
*/
refetchCachedPages?: boolean
}
}
```
Expand Down
5 changes: 4 additions & 1 deletion docs/rtk-query/api/created-api/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ type UseInfiniteQueryOptions = {
refetchOnMountOrArgChange?: boolean | number
selectFromResult?: (result: UseQueryStateDefaultResult) => any
initialPageParam?: PageParam
refetchCachedPages?: boolean
}

type UseInfiniteQueryResult<Data, PageParam> = {
Expand Down Expand Up @@ -514,7 +515,9 @@ type UseInfiniteQueryResult<Data, PageParam> = {
isFetchPreviousPageError: boolean

// A function to force refetch the query - returns a Promise with additional methods
refetch: () => InfiniteQueryActionCreatorResult
refetch: (options?: {
refetchCachedPages?: boolean
}) => InfiniteQueryActionCreatorResult

// Triggers a fetch for the next page, based on the current cache
fetchNextPage: () => InfiniteQueryActionCreatorResult
Expand Down
10 changes: 9 additions & 1 deletion docs/rtk-query/usage/infinite-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,18 @@ The promise returned from `fetchNextPage()` does have [a `promise.abort()` metho

### Refetching

When an infinite query endpoint is refetched (due to tag invalidation, polling, arg change configuration, or manual refetching), RTK Query will try to sequentially refetch all pages currently in the cache. This ensures that the client is always working with the latest data, and avoids stale cursors or duplicate records.
When an infinite query endpoint is refetched (due to tag invalidation, polling, arg change configuration, or manual refetching), RTK Query's default behavior is **sequentially refetching _all_ pages currently in the cache**. This ensures that the client is always working with the latest data, and avoids stale cursors or duplicate records.

If the cache entry is ever removed and then re-added, it will start with only fetching the initial page.

There may be cases when you want a refetch to _only_ refetch the first page, and not any of the other pages in cache (ie, the refetch shrinks the cache from N pages to 1 page). This can be done via the `refetchCachedPages` option, which can be passed in several different places:

- Defined on the endpoint as part of `infiniteQueryOptions`: applies to all attempted refetches
- Passed as an option to a `useInfiniteQuery` hook: applies to all attempted refetches
- Passed as an option to `endpoint.initiate()`, or the `refetch` method available on the `initiate` or hook result objects: applies only to the manually triggered refetch

Overall, the refetch logic defaults to refetching all pages, but will override that with the option provided to the endpoint or a manual refetch.

### Limiting Cache Entry Size

All fetched pages for a given query arg are stored in the `pages` array in that cache entry. By default, there is no limit to the number of stored pages - if you call `fetchNextPage()` 1000 times, `data.pages` will have 1000 pages stored.
Expand Down
7 changes: 7 additions & 0 deletions packages/toolkit/src/query/core/apiState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ export type InfiniteQueryConfigOptions<DataType, PageParam, QueryArg> = {
* direction will be dropped from the cache.
*/
maxPages?: number
/**
* Defaults to `true`. When this is `true` and an infinite query endpoint is refetched
* (due to tag invalidation, polling, arg change configuration, or manual refetching),
* RTK Query will try to sequentially refetch all pages currently in the cache.
* When `false` only the first page will be refetched.
*/
refetchCachedPages?: boolean
}

export type InfiniteData<DataType, PageParam> = {
Expand Down
33 changes: 24 additions & 9 deletions packages/toolkit/src/query/core/buildInitiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export type StartQueryActionCreatorOptions = {
[forceQueryFnSymbol]?: () => QueryReturnValue
}

type RefetchOptions = {
refetchCachedPages?: boolean
}

export type StartInfiniteQueryActionCreatorOptions<
D extends InfiniteQueryDefinition<any, any, any, any, any>,
> = StartQueryActionCreatorOptions & {
Expand All @@ -88,7 +92,7 @@ export type StartInfiniteQueryActionCreatorOptions<
InfiniteQueryArgFrom<D>
>
>,
'initialPageParam'
'initialPageParam' | 'refetchCachedPages'
>
>

Expand Down Expand Up @@ -124,7 +128,7 @@ type AnyActionCreatorResult = SafePromise<any> &
QueryActionCreatorFields & {
arg: any
unwrap(): Promise<any>
refetch(): AnyActionCreatorResult
refetch(options?: RefetchOptions): AnyActionCreatorResult
}

export type QueryActionCreatorResult<
Expand All @@ -142,7 +146,12 @@ export type InfiniteQueryActionCreatorResult<
QueryActionCreatorFields & {
arg: InfiniteQueryArgFrom<D>
unwrap(): Promise<InfiniteData<ResultTypeFrom<D>, PageParamFrom<D>>>
refetch(): InfiniteQueryActionCreatorResult<D>
refetch(
options?: Pick<
StartInfiniteQueryActionCreatorOptions<D>,
'refetchCachedPages'
>,
): InfiniteQueryActionCreatorResult<D>
}

type StartMutationActionCreator<
Expand Down Expand Up @@ -407,16 +416,18 @@ You must add the middleware for RTK-Query to function correctly!`,
if (isQueryDefinition(endpointDefinition)) {
thunk = queryThunk(commonThunkArgs)
} else {
const { direction, initialPageParam } = rest as Pick<
InfiniteQueryThunkArg<any>,
'direction' | 'initialPageParam'
>
const { direction, initialPageParam, refetchCachedPages } =
rest as Pick<
InfiniteQueryThunkArg<any>,
'direction' | 'initialPageParam' | 'refetchCachedPages'
>
thunk = infiniteQueryThunk({
...(commonThunkArgs as InfiniteQueryThunkArg<any>),
// Supply these even if undefined. This helps with a field existence
// check over in `buildSlice.ts`
direction,
initialPageParam,
refetchCachedPages,
})
}

Expand Down Expand Up @@ -465,9 +476,13 @@ You must add the middleware for RTK-Query to function correctly!`,

return result.data
},
refetch: () =>
refetch: (options?: RefetchOptions) =>
dispatch(
queryAction(arg, { subscribe: false, forceRefetch: true }),
queryAction(arg, {
subscribe: false,
forceRefetch: true,
...options,
}),
),
unsubscribe() {
if (subscribe)
Expand Down
33 changes: 21 additions & 12 deletions packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export type InfiniteQueryThunkArg<
endpointName: string
param: unknown
direction?: InfiniteQueryDirection
refetchCachedPages?: boolean
}

type MutationThunkArg = {
Expand Down Expand Up @@ -683,6 +684,12 @@ export function buildThunks<
// Runtime checks should guarantee this is a positive number if provided
const { maxPages = Infinity } = infiniteQueryOptions

// Priority: per-call override > endpoint config > default (true)
const refetchCachedPages =
(arg as InfiniteQueryThunkArg<any>).refetchCachedPages ??
infiniteQueryOptions.refetchCachedPages ??
true

let result: QueryReturnValue

// Start by looking up the existing InfiniteData value from state,
Expand Down Expand Up @@ -740,18 +747,20 @@ export function buildThunks<
} as QueryReturnValue
}

// Fetch remaining pages
for (let i = 1; i < totalPages; i++) {
const param = getNextPageParam(
infiniteQueryOptions,
result.data as InfiniteData<unknown, unknown>,
arg.originalArgs,
)
result = await fetchPage(
result.data as InfiniteData<unknown, unknown>,
param,
maxPages,
)
if (refetchCachedPages) {
// Fetch remaining pages
for (let i = 1; i < totalPages; i++) {
const param = getNextPageParam(
infiniteQueryOptions,
result.data as InfiniteData<unknown, unknown>,
arg.originalArgs,
)
result = await fetchPage(
result.data as InfiniteData<unknown, unknown>,
param,
maxPages,
)
}
}
}

Expand Down
53 changes: 50 additions & 3 deletions packages/toolkit/src/query/react/buildHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,16 @@ export type UseInfiniteQuerySubscriptionOptions<
*/
refetchOnMountOrArgChange?: boolean | number
initialPageParam?: PageParamFrom<D>
/**
* Defaults to `true`. When this is `true` and an infinite query endpoint is refetched
* (due to tag invalidation, polling, arg change configuration, or manual refetching),
* RTK Query will try to sequentially refetch all pages currently in the cache.
* When `false` only the first page will be refetched.
*
* This option applies to all automatic refetches for this subscription (polling, tag invalidation, etc.).
* It can be overridden on a per-call basis using the `refetch()` method.
*/
refetchCachedPages?: boolean
}

export type TypedUseInfiniteQuerySubscription<
Expand All @@ -890,7 +900,13 @@ export type TypedUseInfiniteQuerySubscription<

export type UseInfiniteQuerySubscriptionResult<
D extends InfiniteQueryDefinition<any, any, any, any, any>,
> = Pick<InfiniteQueryActionCreatorResult<D>, 'refetch'> & {
> = {
refetch: (
options?: Pick<
UseInfiniteQuerySubscriptionOptions<D>,
'refetchCachedPages'
>,
) => InfiniteQueryActionCreatorResult<D>
trigger: LazyInfiniteQueryTrigger<D>
fetchNextPage: () => InfiniteQueryActionCreatorResult<D>
fetchPreviousPage: () => InfiniteQueryActionCreatorResult<D>
Expand Down Expand Up @@ -1682,6 +1698,11 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
.initialPageParam
const stableInitialPageParam = useShallowStableValue(initialPageParam)

const refetchCachedPages = (
rest as UseInfiniteQuerySubscriptionOptions<any>
).refetchCachedPages
const stableRefetchCachedPages = useShallowStableValue(refetchCachedPages)

/**
* @todo Change this to `useRef<QueryActionCreatorResult<any>>(undefined)` after upgrading to React 19.
*/
Expand Down Expand Up @@ -1736,6 +1757,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
...(isInfiniteQueryDefinition(endpointDefinitions[endpointName])
? {
initialPageParam: stableInitialPageParam,
refetchCachedPages: stableRefetchCachedPages,
}
: {}),
}),
Expand All @@ -1753,6 +1775,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
stableSubscriptionOptions,
subscriptionRemoved,
stableInitialPageParam,
stableRefetchCachedPages,
endpointName,
])

Expand Down Expand Up @@ -2040,6 +2063,14 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
subscriptionOptionsRef.current = stableSubscriptionOptions
}, [stableSubscriptionOptions])

// Extract and stabilize the hook-level refetchCachedPages option
const hookRefetchCachedPages = (
options as UseInfiniteQuerySubscriptionOptions<any>
).refetchCachedPages
const stableHookRefetchCachedPages = useShallowStableValue(
hookRefetchCachedPages,
)

const trigger: LazyInfiniteQueryTrigger<any> = useCallback(
function (arg: unknown, direction: 'forward' | 'backward') {
let promise: InfiniteQueryActionCreatorResult<any>
Expand All @@ -2065,8 +2096,24 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
const stableArg = useStableQueryArgs(options.skip ? skipToken : arg)

const refetch = useCallback(
() => refetchOrErrorIfUnmounted(promiseRef),
[promiseRef],
(
options?: Pick<
UseInfiniteQuerySubscriptionOptions<any>,
'refetchCachedPages'
>,
) => {
if (!promiseRef.current)
throw new Error(
'Cannot refetch a query that has not been started yet.',
)
// Merge per-call options with hook-level default
const mergedOptions = {
refetchCachedPages:
options?.refetchCachedPages ?? stableHookRefetchCachedPages,
}
return promiseRef.current.refetch(mergedOptions)
},
[promiseRef, stableHookRefetchCachedPages],
)

return useMemo(() => {
Expand Down
Loading
Loading