Skip to content

Commit 79a4295

Browse files
committed
Document infinite query patterns
1 parent 4250870 commit 79a4295

File tree

1 file changed

+258
-2
lines changed

1 file changed

+258
-2
lines changed

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

Lines changed: 258 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ With standard query endpoints:
3131
Infinite queries work similarly, but have a couple additional layers:
3232

3333
- You still specify a "query arg", which is still used to generate the unique cache key for this specific cache entry
34-
- However, there is a separation between the "query arg" used for the cache key, and the "page param" used to fetch a specific page. Since both are useful for determining what to fetch, your `query` and `queryFn` methods will receive a combined object with `{queryArg, pageParam}` as the first argument, instead of just the `queryArg` by itself.
34+
- However, there is a separation between the "query arg" used for the cache key, and the "page param" used to fetch a specific page. Since both are useful for determining what to fetch, **your `query` and `queryFn` methods will receive a combined object with `{queryArg, pageParam}` as the first argument, instead of just the `queryArg` by itself**.
3535
- The `data` field in the cache entry stores a `{pages: DataType[], pageParams: PageParam[]}` structure that contains _all_ of the fetched page results and their corresponding page params used to fetch them.
3636

3737
For example, a Pokemon API endpoint might have a string query arg like `"fire"`, but use a page number as the param to determine which page to fetch out of the results. For a query like `useGetPokemonInfiniteQuery('fire')`, the resulting cache data might look like this:
@@ -75,7 +75,7 @@ The `infiniteQueryOptions` field includes:
7575
- `getPreviousPageParam`: an optional callback that will be used to calculate the previous page param, if you try to fetch backwards.
7676

7777
Both `initialPageParam` and `getNextPageParam` are required, to
78-
ensure the infinite query can properly fetch the next page of data.Also, `initialPageParam` may be specified when using the endpoint, to override the default value for a first fetch. `maxPages` and `getPreviousPageParam` are both optional.
78+
ensure the infinite query can properly fetch the next page of data. Also, `initialPageParam` may be specified when using the endpoint, to override the default value for a first fetch. `maxPages` and `getPreviousPageParam` are both optional.
7979

8080
### Page Param Functions
8181

@@ -114,10 +114,14 @@ const pokemonApi = createApi({
114114
// 3 TS generics: page contents, query arg, page param
115115
getInfinitePokemonWithMax: build.infiniteQuery<Pokemon[], string, number>({
116116
infiniteQueryOptions: {
117+
// Must provide a default initial page param value
117118
initialPageParam: 1,
119+
// Optionally limit the number of cached pages
118120
maxPages: 3,
121+
// Must provide a `getNextPageParam` function
119122
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
120123
lastPageParam + 1,
124+
// Optionally provide a `getPreviousPageParam` function
121125
getPreviousPageParam: (
122126
firstPage,
123127
allPages,
@@ -249,3 +253,255 @@ The endpoint itself only defines `getNextPageParam`, so this example doesn't sup
249253
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.
250254

251255
If you need to limit the number of stored pages (for reasons like memory usage), you can supply a `maxPages` option as part of the endpoint. If provided, fetching a page when already at the max will automatically drop the last page in the opposite direction. For example, with `maxPages: 3` and a cached page params of `[1, 2, 3]`, calling `fetchNextPage()` would result in page `1` being dropped and the new cached pages being `[2, 3, 4]`. From there, calling `fetchNextPage()` would result in `[3, 4, 5]`, or calling `fetchPreviousPage()` would go back to `[1, 2, 3]`.
256+
257+
## Common Infinite Query Patterns
258+
259+
The `getNext/PreviousPageParam` callbacks offer flexibility in how you interact with the backend API.
260+
261+
Here are some examples of common infinite query patterns to show how you might approach different use cases.
262+
263+
### Basic Pagination
264+
265+
For a simple API that just needs page numbers, you can calculate the previous and next page numbers based on the existing page params:
266+
267+
```ts no-transpile
268+
const pokemonApi = createApi({
269+
baseQuery,
270+
endpoints: (build) => ({
271+
getInfinitePokemon: build.infiniteQuery<Pokemon[], string, number>({
272+
infiniteQueryOptions: {
273+
initialPageParam: 0,
274+
getNextPageParam: (lastPage, allPages, lastPageParam) =>
275+
lastPageParam + 1,
276+
getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
277+
return firstPageParam > 0 ? firstPageParam - 1 : undefined
278+
},
279+
},
280+
query({ pageParam }) {
281+
return `https://example.com/listItems?page=${pageParam}`
282+
},
283+
}),
284+
}),
285+
})
286+
```
287+
288+
### Pagination with Sizes
289+
290+
For an API that accepts values like page number and page size and includes total pages in the response, you can calculate whether there are more pages remaining:
291+
292+
```ts no-transpile
293+
type ProjectsResponse = {
294+
projects: Project[]
295+
serverTime: string
296+
totalPages: number
297+
}
298+
299+
type ProjectsInitialPageParam = {
300+
page: number
301+
size: number
302+
}
303+
304+
const projectsApi = createApi({
305+
baseQuery,
306+
endpoints: (build) => ({
307+
projectsPaginated: build.infiniteQuery<
308+
ProjectsResponse,
309+
void,
310+
ProjectsInitialPageParam
311+
>({
312+
infiniteQueryOptions: {
313+
initialPageParam: {
314+
page: 0,
315+
size: 20,
316+
},
317+
getNextPageParam: (
318+
lastPage,
319+
allPages,
320+
lastPageParam,
321+
allPageParams,
322+
) => {
323+
const nextPage = lastPageParam.page + 1
324+
const remainingPages = lastPage?.totalPages - nextPage
325+
326+
if (remainingPages <= 0) {
327+
return undefined
328+
}
329+
330+
return {
331+
...lastPageParam,
332+
page: nextPage,
333+
}
334+
},
335+
getPreviousPageParam: (
336+
firstPage,
337+
allPages,
338+
firstPageParam,
339+
allPageParams,
340+
) => {
341+
const prevPage = firstPageParam.page - 1
342+
if (prevPage < 0) return undefined
343+
344+
return {
345+
...firstPageParam,
346+
page: prevPage,
347+
}
348+
},
349+
},
350+
query: ({ pageParam: { page, size } }) => {
351+
return `https://example.com/api/projectsPaginated?page=${page}&size=${size}`
352+
},
353+
}),
354+
}),
355+
})
356+
```
357+
358+
### Bidirectional Cursors
359+
360+
If the server sends back cursor values in the response, you can use those as the page params for the next and previous requests:
361+
362+
```ts no-transpile
363+
type ProjectsCursorPaginated = {
364+
projects: Project[]
365+
serverTime: string
366+
pageInfo: {
367+
startCursor: number
368+
endCursor: number
369+
hasNextPage: boolean
370+
hasPreviousPage: boolean
371+
}
372+
}
373+
374+
type ProjectsInitialPageParam = {
375+
before?: number
376+
around?: number
377+
after?: number
378+
limit: number
379+
}
380+
type QueryParamLimit = number
381+
382+
const projectsApi = createApi({
383+
baseQuery,
384+
endpoints: (build) => ({
385+
getProjectsBidirectionalCursor: build.infiniteQuery<
386+
ProjectsCursorPaginated,
387+
QueryParamLimit,
388+
ProjectsInitialPageParam
389+
>({
390+
infiniteQueryOptions: {
391+
initialPageParam: { limit: 10 },
392+
getPreviousPageParam: (
393+
firstPage,
394+
allPages,
395+
firstPageParam,
396+
allPageParams,
397+
) => {
398+
if (!firstPage.pageInfo.hasPreviousPage) {
399+
return undefined
400+
}
401+
return {
402+
before: firstPage.pageInfo.startCursor,
403+
limit: firstPageParam.limit,
404+
}
405+
},
406+
getNextPageParam: (
407+
lastPage,
408+
allPages,
409+
lastPageParam,
410+
allPageParams,
411+
) => {
412+
if (!lastPage.pageInfo.hasNextPage) {
413+
return undefined
414+
}
415+
return {
416+
after: lastPage.pageInfo.endCursor,
417+
limit: lastPageParam.limit,
418+
}
419+
},
420+
},
421+
query: ({ pageParam: { before, after, around, limit } }) => {
422+
const params = new URLSearchParams()
423+
params.append('limit', String(limit))
424+
if (after != null) {
425+
params.append('after', String(after))
426+
} else if (before != null) {
427+
params.append('before', String(before))
428+
} else if (around != null) {
429+
params.append('around', String(around))
430+
}
431+
432+
return `https://example.com/api/projectsBidirectionalCursor?${params.toString()}`,
433+
},
434+
}),
435+
}),
436+
})
437+
```
438+
439+
### Limit and Offset
440+
441+
If the API expects a combination of limit and offset values, those can also be calculated based on the responses and page params.
442+
443+
```ts no-transpile
444+
export type ProjectsResponse = {
445+
projects: Project[]
446+
numFound: number
447+
serverTime: string
448+
}
449+
450+
type ProjectsInitialPageParam = {
451+
offset: number
452+
limit: number
453+
}
454+
455+
const projectsApi = createApi({
456+
baseQuery,
457+
endpoints: (build) => ({
458+
projectsLimitOffset: build.infiniteQuery<
459+
ProjectsResponse,
460+
void,
461+
ProjectsInitialPageParam
462+
>({
463+
infiniteQueryOptions: {
464+
initialPageParam: {
465+
offset: 0,
466+
limit: 20,
467+
},
468+
getNextPageParam: (
469+
lastPage,
470+
allPages,
471+
lastPageParam,
472+
allPageParams,
473+
) => {
474+
const nextOffset = lastPageParam.offset + lastPageParam.limit
475+
const remainingItems = lastPage?.numFound - nextOffset
476+
477+
if (remainingItems <= 0) {
478+
return undefined
479+
}
480+
481+
return {
482+
...lastPageParam,
483+
offset: nextOffset,
484+
}
485+
},
486+
getPreviousPageParam: (
487+
firstPage,
488+
allPages,
489+
firstPageParam,
490+
allPageParams,
491+
) => {
492+
const prevOffset = firstPageParam.offset - firstPageParam.limit
493+
if (prevOffset < 0) return undefined
494+
495+
return {
496+
...firstPageParam,
497+
offset: firstPageParam.offset - firstPageParam.limit,
498+
}
499+
},
500+
},
501+
query: ({ pageParam: { offset, limit } }) => {
502+
return `https://example.com/api/projectsLimitOffset?offset=${offset}&limit=${limit}`
503+
},
504+
}),
505+
}),
506+
})
507+
```

0 commit comments

Comments
 (0)