Skip to content

Commit c13e494

Browse files
Add refetchCachedPages option for infinite query (#5016)
* Add refetchCachedPages option for infinite query * Add refetchCachedPages as option to thunks * Add refetchCachedPages as hook and refetch option * Document refetchCachedPages --------- Co-authored-by: Mark Erikson <[email protected]>
1 parent 0cd0fe9 commit c13e494

File tree

9 files changed

+1053
-26
lines changed

9 files changed

+1053
-26
lines changed

docs/rtk-query/api/createApi.mdx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,13 @@ export type InfiniteQueryDefinition<
329329
* direction will be dropped from the cache.
330330
*/
331331
maxPages?: number
332+
/**
333+
* Defaults to `true`. When this is `true` and an infinite query endpoint is refetched
334+
* (due to tag invalidation, polling, arg change configuration, or manual refetching),
335+
* RTK Query will try to sequentially refetch all pages currently in the cache.
336+
* When `false` only the first page will be refetched.
337+
*/
338+
refetchCachedPages?: boolean
332339
}
333340
}
334341
```

docs/rtk-query/api/created-api/hooks.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ type UseInfiniteQueryOptions = {
463463
refetchOnMountOrArgChange?: boolean | number
464464
selectFromResult?: (result: UseQueryStateDefaultResult) => any
465465
initialPageParam?: PageParam
466+
refetchCachedPages?: boolean
466467
}
467468

468469
type UseInfiniteQueryResult<Data, PageParam> = {
@@ -514,7 +515,9 @@ type UseInfiniteQueryResult<Data, PageParam> = {
514515
isFetchPreviousPageError: boolean
515516

516517
// A function to force refetch the query - returns a Promise with additional methods
517-
refetch: () => InfiniteQueryActionCreatorResult
518+
refetch: (options?: {
519+
refetchCachedPages?: boolean
520+
}) => InfiniteQueryActionCreatorResult
518521

519522
// Triggers a fetch for the next page, based on the current cache
520523
fetchNextPage: () => InfiniteQueryActionCreatorResult

docs/rtk-query/usage/infinite-queries.mdx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,18 @@ The promise returned from `fetchNextPage()` does have [a `promise.abort()` metho
277277

278278
### Refetching
279279

280-
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.
280+
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.
281281

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

284+
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:
285+
286+
- Defined on the endpoint as part of `infiniteQueryOptions`: applies to all attempted refetches
287+
- Passed as an option to a `useInfiniteQuery` hook: applies to all attempted refetches
288+
- 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
289+
290+
Overall, the refetch logic defaults to refetching all pages, but will override that with the option provided to the endpoint or a manual refetch.
291+
284292
### Limiting Cache Entry Size
285293

286294
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.

packages/toolkit/src/query/core/apiState.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ export type InfiniteQueryConfigOptions<DataType, PageParam, QueryArg> = {
5858
* direction will be dropped from the cache.
5959
*/
6060
maxPages?: number
61+
/**
62+
* Defaults to `true`. When this is `true` and an infinite query endpoint is refetched
63+
* (due to tag invalidation, polling, arg change configuration, or manual refetching),
64+
* RTK Query will try to sequentially refetch all pages currently in the cache.
65+
* When `false` only the first page will be refetched.
66+
*/
67+
refetchCachedPages?: boolean
6168
}
6269

6370
export type InfiniteData<DataType, PageParam> = {

packages/toolkit/src/query/core/buildInitiate.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ export type StartQueryActionCreatorOptions = {
7474
[forceQueryFnSymbol]?: () => QueryReturnValue
7575
}
7676

77+
type RefetchOptions = {
78+
refetchCachedPages?: boolean
79+
}
80+
7781
export type StartInfiniteQueryActionCreatorOptions<
7882
D extends InfiniteQueryDefinition<any, any, any, any, any>,
7983
> = StartQueryActionCreatorOptions & {
@@ -88,7 +92,7 @@ export type StartInfiniteQueryActionCreatorOptions<
8892
InfiniteQueryArgFrom<D>
8993
>
9094
>,
91-
'initialPageParam'
95+
'initialPageParam' | 'refetchCachedPages'
9296
>
9397
>
9498

@@ -124,7 +128,7 @@ type AnyActionCreatorResult = SafePromise<any> &
124128
QueryActionCreatorFields & {
125129
arg: any
126130
unwrap(): Promise<any>
127-
refetch(): AnyActionCreatorResult
131+
refetch(options?: RefetchOptions): AnyActionCreatorResult
128132
}
129133

130134
export type QueryActionCreatorResult<
@@ -142,7 +146,12 @@ export type InfiniteQueryActionCreatorResult<
142146
QueryActionCreatorFields & {
143147
arg: InfiniteQueryArgFrom<D>
144148
unwrap(): Promise<InfiniteData<ResultTypeFrom<D>, PageParamFrom<D>>>
145-
refetch(): InfiniteQueryActionCreatorResult<D>
149+
refetch(
150+
options?: Pick<
151+
StartInfiniteQueryActionCreatorOptions<D>,
152+
'refetchCachedPages'
153+
>,
154+
): InfiniteQueryActionCreatorResult<D>
146155
}
147156

148157
type StartMutationActionCreator<
@@ -407,16 +416,18 @@ You must add the middleware for RTK-Query to function correctly!`,
407416
if (isQueryDefinition(endpointDefinition)) {
408417
thunk = queryThunk(commonThunkArgs)
409418
} else {
410-
const { direction, initialPageParam } = rest as Pick<
411-
InfiniteQueryThunkArg<any>,
412-
'direction' | 'initialPageParam'
413-
>
419+
const { direction, initialPageParam, refetchCachedPages } =
420+
rest as Pick<
421+
InfiniteQueryThunkArg<any>,
422+
'direction' | 'initialPageParam' | 'refetchCachedPages'
423+
>
414424
thunk = infiniteQueryThunk({
415425
...(commonThunkArgs as InfiniteQueryThunkArg<any>),
416426
// Supply these even if undefined. This helps with a field existence
417427
// check over in `buildSlice.ts`
418428
direction,
419429
initialPageParam,
430+
refetchCachedPages,
420431
})
421432
}
422433

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

466477
return result.data
467478
},
468-
refetch: () =>
479+
refetch: (options?: RefetchOptions) =>
469480
dispatch(
470-
queryAction(arg, { subscribe: false, forceRefetch: true }),
481+
queryAction(arg, {
482+
subscribe: false,
483+
forceRefetch: true,
484+
...options,
485+
}),
471486
),
472487
unsubscribe() {
473488
if (subscribe)

packages/toolkit/src/query/core/buildThunks.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export type InfiniteQueryThunkArg<
163163
endpointName: string
164164
param: unknown
165165
direction?: InfiniteQueryDirection
166+
refetchCachedPages?: boolean
166167
}
167168

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

687+
// Priority: per-call override > endpoint config > default (true)
688+
const refetchCachedPages =
689+
(arg as InfiniteQueryThunkArg<any>).refetchCachedPages ??
690+
infiniteQueryOptions.refetchCachedPages ??
691+
true
692+
686693
let result: QueryReturnValue
687694

688695
// Start by looking up the existing InfiniteData value from state,
@@ -740,18 +747,20 @@ export function buildThunks<
740747
} as QueryReturnValue
741748
}
742749

743-
// Fetch remaining pages
744-
for (let i = 1; i < totalPages; i++) {
745-
const param = getNextPageParam(
746-
infiniteQueryOptions,
747-
result.data as InfiniteData<unknown, unknown>,
748-
arg.originalArgs,
749-
)
750-
result = await fetchPage(
751-
result.data as InfiniteData<unknown, unknown>,
752-
param,
753-
maxPages,
754-
)
750+
if (refetchCachedPages) {
751+
// Fetch remaining pages
752+
for (let i = 1; i < totalPages; i++) {
753+
const param = getNextPageParam(
754+
infiniteQueryOptions,
755+
result.data as InfiniteData<unknown, unknown>,
756+
arg.originalArgs,
757+
)
758+
result = await fetchPage(
759+
result.data as InfiniteData<unknown, unknown>,
760+
param,
761+
maxPages,
762+
)
763+
}
755764
}
756765
}
757766

packages/toolkit/src/query/react/buildHooks.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,16 @@ export type UseInfiniteQuerySubscriptionOptions<
870870
*/
871871
refetchOnMountOrArgChange?: boolean | number
872872
initialPageParam?: PageParamFrom<D>
873+
/**
874+
* Defaults to `true`. When this is `true` and an infinite query endpoint is refetched
875+
* (due to tag invalidation, polling, arg change configuration, or manual refetching),
876+
* RTK Query will try to sequentially refetch all pages currently in the cache.
877+
* When `false` only the first page will be refetched.
878+
*
879+
* This option applies to all automatic refetches for this subscription (polling, tag invalidation, etc.).
880+
* It can be overridden on a per-call basis using the `refetch()` method.
881+
*/
882+
refetchCachedPages?: boolean
873883
}
874884

875885
export type TypedUseInfiniteQuerySubscription<
@@ -890,7 +900,13 @@ export type TypedUseInfiniteQuerySubscription<
890900

891901
export type UseInfiniteQuerySubscriptionResult<
892902
D extends InfiniteQueryDefinition<any, any, any, any, any>,
893-
> = Pick<InfiniteQueryActionCreatorResult<D>, 'refetch'> & {
903+
> = {
904+
refetch: (
905+
options?: Pick<
906+
UseInfiniteQuerySubscriptionOptions<D>,
907+
'refetchCachedPages'
908+
>,
909+
) => InfiniteQueryActionCreatorResult<D>
894910
trigger: LazyInfiniteQueryTrigger<D>
895911
fetchNextPage: () => InfiniteQueryActionCreatorResult<D>
896912
fetchPreviousPage: () => InfiniteQueryActionCreatorResult<D>
@@ -1682,6 +1698,11 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
16821698
.initialPageParam
16831699
const stableInitialPageParam = useShallowStableValue(initialPageParam)
16841700

1701+
const refetchCachedPages = (
1702+
rest as UseInfiniteQuerySubscriptionOptions<any>
1703+
).refetchCachedPages
1704+
const stableRefetchCachedPages = useShallowStableValue(refetchCachedPages)
1705+
16851706
/**
16861707
* @todo Change this to `useRef<QueryActionCreatorResult<any>>(undefined)` after upgrading to React 19.
16871708
*/
@@ -1736,6 +1757,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
17361757
...(isInfiniteQueryDefinition(endpointDefinitions[endpointName])
17371758
? {
17381759
initialPageParam: stableInitialPageParam,
1760+
refetchCachedPages: stableRefetchCachedPages,
17391761
}
17401762
: {}),
17411763
}),
@@ -1753,6 +1775,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
17531775
stableSubscriptionOptions,
17541776
subscriptionRemoved,
17551777
stableInitialPageParam,
1778+
stableRefetchCachedPages,
17561779
endpointName,
17571780
])
17581781

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

2066+
// Extract and stabilize the hook-level refetchCachedPages option
2067+
const hookRefetchCachedPages = (
2068+
options as UseInfiniteQuerySubscriptionOptions<any>
2069+
).refetchCachedPages
2070+
const stableHookRefetchCachedPages = useShallowStableValue(
2071+
hookRefetchCachedPages,
2072+
)
2073+
20432074
const trigger: LazyInfiniteQueryTrigger<any> = useCallback(
20442075
function (arg: unknown, direction: 'forward' | 'backward') {
20452076
let promise: InfiniteQueryActionCreatorResult<any>
@@ -2065,8 +2096,24 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
20652096
const stableArg = useStableQueryArgs(options.skip ? skipToken : arg)
20662097

20672098
const refetch = useCallback(
2068-
() => refetchOrErrorIfUnmounted(promiseRef),
2069-
[promiseRef],
2099+
(
2100+
options?: Pick<
2101+
UseInfiniteQuerySubscriptionOptions<any>,
2102+
'refetchCachedPages'
2103+
>,
2104+
) => {
2105+
if (!promiseRef.current)
2106+
throw new Error(
2107+
'Cannot refetch a query that has not been started yet.',
2108+
)
2109+
// Merge per-call options with hook-level default
2110+
const mergedOptions = {
2111+
refetchCachedPages:
2112+
options?.refetchCachedPages ?? stableHookRefetchCachedPages,
2113+
}
2114+
return promiseRef.current.refetch(mergedOptions)
2115+
},
2116+
[promiseRef, stableHookRefetchCachedPages],
20702117
)
20712118

20722119
return useMemo(() => {

0 commit comments

Comments
 (0)