Skip to content

Commit 4076eff

Browse files
authored
feat: remove refetchPage (#4990)
* feat: remove refetchPage * fix: unused type * fix: types generics * refactor: remove ResetQueryFilters type * docs: migration guide: new feature * docs: migration guide wording * docs: new features typo
1 parent 84ae16b commit 4076eff

File tree

12 files changed

+51
-468
lines changed

12 files changed

+51
-468
lines changed

docs/react/guides/infinite-queries.md

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -103,33 +103,6 @@ function Projects() {
103103

104104
When an infinite query becomes `stale` and needs to be refetched, each group is fetched `sequentially`, starting from the first one. This ensures that even if the underlying data is mutated, we're not using stale cursors and potentially getting duplicates or skipping records. If an infinite query's results are ever removed from the queryCache, the pagination restarts at the initial state with only the initial group being requested.
105105

106-
### refetchPage
107-
108-
If you only want to actively refetch a subset of all pages, you can pass the `refetchPage` function to `refetch` returned from `useInfiniteQuery`.
109-
110-
[//]: # 'Example2'
111-
112-
```tsx
113-
const { refetch } = useInfiniteQuery({
114-
queryKey: ['projects'],
115-
queryFn: fetchProjects,
116-
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
117-
})
118-
119-
// only refetch the first page
120-
refetch({ refetchPage: (page, index) => index === 0 })
121-
```
122-
123-
[//]: # 'Example2'
124-
125-
You can also pass this function as part of the 2nd argument (`queryFilters`) to [queryClient.refetchQueries](../reference/QueryClient#queryclientrefetchqueries), [queryClient.invalidateQueries](../reference/QueryClient#queryclientinvalidatequeries) or [queryClient.resetQueries](../reference/QueryClient#queryclientresetqueries).
126-
127-
**Signature**
128-
129-
- `refetchPage: (page: TData, index: number, allPages: TData[]) => boolean`
130-
131-
The function is executed for each page, and only pages where this function returns `true` will be refetched.
132-
133106
## What if I need to pass custom information to my query function?
134107

135108
By default, the variable returned from `getNextPageParam` will be supplied to the query function, but in some cases, you may want to override this. You can pass custom variables to the `fetchNextPage` function which will override the default variable like so:

docs/react/guides/migrating-to-v5.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,14 @@ const { data } = useQuery(
257257
)
258258
```
259259

260+
### Removed `refetchPage` in favor of `maxPages`
261+
262+
In v4, we introduced the possibility to define the pages to refetch for infinite queries with the `refetchPage` function.
263+
264+
However, refetching all pages might lead to UI inconsistencies. Also, this option is available on e.g. `queryClient.refetchQueries`, but it only does something for infinite queries, not "normal" queries.
265+
266+
The v5 includes a new `maxPages` option for infinite queries to limit the number of pages to store in the query data and to refetch. This new feature handles the use cases initially identified for the `refetchPage` page feature without the related issues.
267+
260268
[//]: # 'FrameworkBreakingChanges'
261269

262270
## React Query Breaking Changes
@@ -309,3 +317,20 @@ Lastly the a new derived `isLoading` flag has been added to the queries that is
309317
To understand the reasoning behing this change checkout the [v5 roadmap discussion](https://github.com/TanStack/query/discussions/4252).
310318

311319
[//]: # 'FrameworkBreakingChanges'
320+
321+
322+
[//]: # 'NewFeatures'
323+
324+
## New Features 🚀
325+
326+
### Eterneral list: scalable infinite query with new maxPages option
327+
328+
Infinite queries are great when infinite scroll or pagination are needed.
329+
However, the more pages you fetch, the more memory you consume, and this also slows down the query refetching process as all the pages are sequentially refetched.
330+
331+
Version 5 has a new `maxPages` option for infinite queries, which allows developers to limit the number of pages that are stored in the query data and subsequently refetched.
332+
You can adjust the `maxPages` value according to the UX and refetching performance you want to deliver.
333+
334+
Note that the infinite list must be bi-directional, which requires both `getNextPageParam` and `getPreviousPageParam` to be defined.
335+
336+
[//]: # 'NewFeatures'

docs/react/reference/QueryClient.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,6 @@ await queryClient.invalidateQueries({
307307
- When set to `inactive`, only queries that match the refetch predicate and are NOT actively being rendered via `useQuery` and friends will be refetched in the background.
308308
- When set to `all`, all queries that match the refetch predicate will be refetched in the background.
309309
- When set to `none`, no queries will be refetched, and those that match the refetch predicate will be marked as invalid only.
310-
- `refetchPage: (page: TData, index: number, allPages: TData[]) => boolean`
311-
- Only for [Infinite Queries](../guides/infinite-queries#refetchpage)
312-
- Use this function to specify which pages should be refetched
313310
- `options?: InvalidateOptions`:
314311
- `throwOnError?: boolean`
315312
- When set to `true`, this method will throw if any of the query refetch tasks fail.
@@ -341,9 +338,6 @@ await queryClient.refetchQueries({ queryKey: ['posts', 1], type: 'active', exact
341338
**Options**
342339

343340
- `filters?: QueryFilters`: [Query Filters](../guides/filters#query-filters)
344-
- `refetchPage: (page: TData, index: number, allPages: TData[]) => boolean`
345-
- Only for [Infinite Queries](../guides/infinite-queries#refetchpage)
346-
- Use this function to specify which pages should be refetched
347341
- `options?: RefetchOptions`:
348342
- `throwOnError?: boolean`
349343
- When set to `true`, this method will throw if any of the query refetch tasks fail.
@@ -408,9 +402,6 @@ queryClient.resetQueries({ queryKey, exact: true })
408402
**Options**
409403

410404
- `filters?: QueryFilters`: [Query Filters](../guides/filters#query-filters)
411-
- `refetchPage: (page: TData, index: number, allPages: TData[]) => boolean`
412-
- Only for [Infinite Queries](../guides/infinite-queries#refetchpage)
413-
- Use this function to specify which pages should be refetched
414405
- `options?: ResetOptions`:
415406
- `throwOnError?: boolean`
416407
- When set to `true`, this method will throw if any of the query refetch tasks fail.

packages/query-core/src/infiniteQueryBehavior.ts

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import type { QueryBehavior } from './query'
22
import { addToEnd, addToStart } from './utils'
3-
import type {
4-
InfiniteData,
5-
QueryFunctionContext,
6-
QueryOptions,
7-
RefetchQueryFilters,
8-
} from './types'
3+
import type { InfiniteData, QueryFunctionContext, QueryOptions } from './types'
94

105
export function infiniteQueryBehavior<
116
TQueryFnData,
@@ -15,8 +10,6 @@ export function infiniteQueryBehavior<
1510
return {
1611
onFetch: (context) => {
1712
context.fetchFn = () => {
18-
const refetchPage: RefetchQueryFilters['refetchPage'] | undefined =
19-
context.fetchOptions?.meta?.refetchPage
2013
const fetchMore = context.fetchOptions?.meta?.fetchMore
2114
const pageParam = fetchMore?.pageParam
2215
const isFetchingNextPage = fetchMore?.direction === 'forward'
@@ -127,33 +120,16 @@ export function infiniteQueryBehavior<
127120

128121
const manual = typeof context.options.getNextPageParam === 'undefined'
129122

130-
const shouldFetchFirstPage =
131-
refetchPage && oldPages[0]
132-
? refetchPage(oldPages[0], 0, oldPages)
133-
: true
134-
135123
// Fetch first page
136-
promise = shouldFetchFirstPage
137-
? fetchPage([], manual, oldPageParams[0])
138-
: Promise.resolve(buildNewPages([], oldPageParams[0], oldPages[0]))
124+
promise = fetchPage([], manual, oldPageParams[0])
139125

140126
// Fetch remaining pages
141127
for (let i = 1; i < oldPages.length; i++) {
142128
promise = promise.then((pages) => {
143-
const shouldFetchNextPage =
144-
refetchPage && oldPages[i]
145-
? refetchPage(oldPages[i], i, oldPages)
146-
: true
147-
148-
if (shouldFetchNextPage) {
149-
const param = manual
150-
? oldPageParams[i]
151-
: getNextPageParam(context.options, pages)
152-
return fetchPage(pages, manual, param)
153-
}
154-
return Promise.resolve(
155-
buildNewPages(pages, oldPageParams[i], oldPages[i]),
156-
)
129+
const param = manual
130+
? oldPageParams[i]
131+
: getNextPageParam(context.options, pages)
132+
return fetchPage(pages, manual, param)
157133
})
158134
}
159135
}

packages/query-core/src/queryClient.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import type {
2323
RefetchOptions,
2424
RefetchQueryFilters,
2525
ResetOptions,
26-
ResetQueryFilters,
2726
SetDataOptions,
2827
RegisteredError,
2928
} from './types'
@@ -193,13 +192,10 @@ export class QueryClient {
193192
})
194193
}
195194

196-
resetQueries<TPageData = unknown>(
197-
filters?: ResetQueryFilters<TPageData>,
198-
options?: ResetOptions,
199-
): Promise<void> {
195+
resetQueries(filters?: QueryFilters, options?: ResetOptions): Promise<void> {
200196
const queryCache = this.#queryCache
201197

202-
const refetchFilters: RefetchQueryFilters<TPageData> = {
198+
const refetchFilters: RefetchQueryFilters = {
203199
type: 'active',
204200
...filters,
205201
}
@@ -229,8 +225,8 @@ export class QueryClient {
229225
return Promise.all(promises).then(noop).catch(noop)
230226
}
231227

232-
invalidateQueries<TPageData = unknown>(
233-
filters: InvalidateQueryFilters<TPageData> = {},
228+
invalidateQueries(
229+
filters: InvalidateQueryFilters = {},
234230
options: InvalidateOptions = {},
235231
): Promise<void> {
236232
return notifyManager.batch(() => {
@@ -241,16 +237,16 @@ export class QueryClient {
241237
if (filters.refetchType === 'none') {
242238
return Promise.resolve()
243239
}
244-
const refetchFilters: RefetchQueryFilters<TPageData> = {
240+
const refetchFilters: RefetchQueryFilters = {
245241
...filters,
246242
type: filters.refetchType ?? filters.type ?? 'active',
247243
}
248244
return this.refetchQueries(refetchFilters, options)
249245
})
250246
}
251247

252-
refetchQueries<TPageData = unknown>(
253-
filters: RefetchQueryFilters<TPageData> = {},
248+
refetchQueries(
249+
filters: RefetchQueryFilters = {},
254250
options?: RefetchOptions,
255251
): Promise<void> {
256252
const promises = notifyManager.batch(() =>
@@ -261,7 +257,6 @@ export class QueryClient {
261257
query.fetch(undefined, {
262258
...options,
263259
cancelRefetch: options?.cancelRefetch ?? true,
264-
meta: { refetchPage: filters.refetchPage },
265260
}),
266261
),
267262
)

packages/query-core/src/queryObserver.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import type {
2-
DefaultedQueryObserverOptions,
3-
RefetchPageFilters,
4-
RegisteredError,
5-
} from './types'
1+
import type { DefaultedQueryObserverOptions, RegisteredError } from './types'
62
import {
73
isServer,
84
isValidTimeout,
@@ -260,15 +256,11 @@ export class QueryObserver<
260256
return this.#currentQuery
261257
}
262258

263-
refetch<TPageData>({
264-
refetchPage,
265-
...options
266-
}: RefetchOptions & RefetchPageFilters<TPageData> = {}): Promise<
259+
refetch({ ...options }: RefetchOptions = {}): Promise<
267260
QueryObserverResult<TData, TError>
268261
> {
269262
return this.fetch({
270263
...options,
271-
meta: { refetchPage },
272264
})
273265
}
274266

packages/query-core/src/tests/infiniteQueryBehavior.test.tsx

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -44,84 +44,6 @@ describe('InfiniteQueryBehavior', () => {
4444
unsubscribe()
4545
})
4646

47-
test('InfiniteQueryBehavior should not refetch the first page if another page refetched', async () => {
48-
const key = queryKey()
49-
let abortSignal: AbortSignal | null = null
50-
51-
const queryFnSpy = jest
52-
.fn()
53-
.mockImplementation(({ pageParam = 1, signal }) => {
54-
abortSignal = signal
55-
return pageParam
56-
})
57-
58-
const observer = new InfiniteQueryObserver<number>(queryClient, {
59-
queryKey: key,
60-
queryFn: queryFnSpy,
61-
getNextPageParam: (lastPage) => lastPage + 1,
62-
})
63-
64-
let observerResult:
65-
| InfiniteQueryObserverResult<unknown, unknown>
66-
| undefined
67-
68-
const unsubscribe = observer.subscribe((result) => {
69-
observerResult = result
70-
})
71-
72-
// Wait for the first page to be fetched
73-
await waitFor(() =>
74-
expect(observerResult).toMatchObject({
75-
isFetching: false,
76-
data: { pages: [1] },
77-
}),
78-
)
79-
80-
expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
81-
queryKey: key,
82-
pageParam: undefined,
83-
meta: undefined,
84-
signal: abortSignal,
85-
})
86-
87-
queryFnSpy.mockClear()
88-
89-
// Fetch the second page
90-
await observer.fetchNextPage()
91-
92-
expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
93-
queryKey: key,
94-
pageParam: 2,
95-
meta: undefined,
96-
signal: abortSignal,
97-
})
98-
99-
expect(observerResult).toMatchObject({
100-
isFetching: false,
101-
data: { pages: [1, 2] },
102-
})
103-
104-
queryFnSpy.mockClear()
105-
106-
// Refetch the second page
107-
await queryClient.refetchQueries({
108-
refetchPage: (_page, index) => index === 1,
109-
})
110-
111-
expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
112-
queryKey: key,
113-
pageParam: 2,
114-
meta: undefined,
115-
signal: abortSignal,
116-
})
117-
118-
expect(observerResult).toMatchObject({
119-
data: { pages: [1, 2] },
120-
})
121-
122-
unsubscribe()
123-
})
124-
12547
test('InfiniteQueryBehavior should apply the maxPages option to limit the number of pages', async () => {
12648
const key = queryKey()
12749
let abortSignal: AbortSignal | null = null

0 commit comments

Comments
 (0)