Skip to content

Commit f5aaa4f

Browse files
committed
Fill out infinite queries usage guide
1 parent b873043 commit f5aaa4f

File tree

3 files changed

+97
-16
lines changed

3 files changed

+97
-16
lines changed

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

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ description: 'RTK Query > Usage > Infinite Queries: fetching many data pages fro
1212

1313
## Overview
1414

15-
Rendering lists that can additively "load more" data onto an existing set of data or "infinite scroll" is also a very common UI pattern.
15+
Rendering lists that can additively "load more" data onto an existing set of data or "infinite scroll" is a common UI pattern.
1616

1717
RTK Query supports this use case via "infinite query" endpoints. Infinite Query endpoints are similar to standard query endpoints, in that they fetch data and cache the results. However, infinite query endpoints have the ability to fetch "next" and "previous" pages, and contain all related fetched pages in a single cache entry.
1818

@@ -24,17 +24,17 @@ RTK Query's support for infinite queries is modeled after [React Query's infinit
2424

2525
With standard query endpoints:
2626

27-
- You specify the "query arg" value, which is passed to either the `query` function or the `queryFn` function that will calculate the desired URL or do the actual fetching
27+
- You specify the "query arg" value, which is passed to the `query` or `queryFn` function that will calculate the desired URL or do the actual fetching
2828
- The query arg is also serialized to generate the unique internal key for this specific cache entry
2929
- The single response value is directly stored as the `data` field in the cache entry
3030

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
3434
- However, there is a separation between the "query arg" used for the cache key, and the "page param" used to fetch a specific page. This means the page param is what will be passed to your `query` or `queryFn` methods.
35-
- The `data` field in the cache entry stores a `{pages: Array<DataType>, pageParams: PageParam[]}` structure that contains _all_ of the fetched page results and their corresponding page params used to fetch them.
35+
- 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

37-
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. The resulting cache data might look like this:
37+
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:
3838

3939
```ts no-transpile
4040
{
@@ -61,7 +61,7 @@ This structure allows flexibility in how your UI chooses to render the data (sho
6161

6262
## Defining Infinite Query Endpoints
6363

64-
Infinite query endpoints are defined by returning an object inside the `endpoints` section of `createApi`, and defining the fields using the `build.infiniteQuery()` method. They are an extension of standard query endpoints - you can specify [the same options as standard queries](./queries.mdx#defining-query-endpoints) (providing either `query` or `queryFn`, customizing with `transformResponse`, lifecycles with `onCacheEntryAdded` and `onQueryStarted`, etc). However, they also require an additional `infiniteQueryOptions` field to specify the infinite query behavior.
64+
Infinite query endpoints are defined by returning an object inside the `endpoints` section of `createApi`, and defining the fields using the `build.infiniteQuery()` method. They are an extension of standard query endpoints - you can specify [the same options as standard queries](./queries.mdx#defining-query-endpoints) (providing either `query` or `queryFn`, customizing with `transformResponse`, lifecycles with `onCacheEntryAdded` and `onQueryStarted`, defining tags, etc). However, they also require an additional `infiniteQueryOptions` field to specify the infinite query behavior.
6565

6666
With TypeScript, you must supply 3 generic arguments: `build.infiniteQuery<ResultType, QueryArg, PageParam>`, where `ResultType` is the contents of a single page, `QueryArg` is the type passed in as the cache key, and `PageParam` is the value that will be passed to `query/queryFn` to make the rest. If there is no argument, use `void` for the arg type instead.
6767

@@ -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. `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

@@ -84,15 +84,17 @@ ensure the infinite query can properly fetch the next page of data.Also, `initia
8484
```ts
8585
export type PageParamFunction<DataType, PageParam> = (
8686
currentPage: DataType,
87-
allPages: Array<DataType>,
87+
allPages: DataType[],
8888
currentPageParam: PageParam,
89-
allPageParams: Array<PageParam>,
89+
allPageParams: PageParam[],
9090
) => PageParam | undefined | null
9191
```
9292
93-
This enables a number of possible infinite query use cases, including cursor-based and limit+offset-based queries.
93+
A page param can be any value at all: numbers, strings, objects, arrays, etc. Since the existing page param values are stored in Redux state, you should still treat those immutably. For example, if you had a param structure like `{page: Number, filters: Filters}`, incrementing the page would look like `return {...currentPageParam, page: currentPageParam.page + 1}`.
9494
95-
The "current" arguments will be either the last page for `getNext`, or the first page for `getPrevious`.
95+
Since both actual page contents and page params are passed in, you can calculate new page params based on any of those. This enables a number of possible infinite query use cases, including cursor-based and limit+offset-based queries.
96+
97+
The "current" arguments will be either the last page for `getNextPageParam`, or the first page for `getPreviousPageParam`.
9698
9799
If there is no possible page to fetch in that direction, the callback should return `undefined`.
98100
@@ -134,7 +136,7 @@ const pokemonApi = createApi({
134136

135137
## Performing Infinite Queries with React Hooks
136138

137-
[Similar to query endpoints](./queries.mdx#performing-queries-with-react-hooks), RTK Query will automatically generate React hooks for infinite query endpoints based on the name of the endpoint. An endpoint field with `getPokemon: build.infiniteQuery()` will generate a hook named `useGetPokemonInfiniteQuery`.
139+
[Similar to query endpoints](./queries.mdx#performing-queries-with-react-hooks), RTK Query will automatically generate React hooks for infinite query endpoints based on the name of the endpoint. An endpoint field with `getPokemon: build.infiniteQuery()` will generate a hook named `useGetPokemonInfiniteQuery`, as well as a generically-named hook attached to the endpoint, like `api.endpoints.getPokemon.useInfiniteQuery`.
138140

139141
### Hook Types
140142

@@ -155,7 +157,7 @@ The query hooks expect two parameters: `(queryArg?, queryOptions?)`.
155157

156158
The `queryOptions` object accepts [all the same parameters as `useQuery`](./queries.mdx#query-hook-options), including `skip`, `selectFromResult`, and refetching/polling options.
157159

158-
Unlike normal query hooks, your `query` or `queryFn` callbacks will receive a "page param" value to generate the URL or make the request. By default, the `initialPageParam` value specified in the endpoint will be used to make the first request, and then your `getNext/PreviousPageParam` callbacks will be used to calculate further page params as you fetch forwards or backwards.
160+
Unlike normal query hooks, your `query` or `queryFn` callbacks will receive a "page param" value to generate the URL or make the request, instead of the "query arg" that was passed to the hook. By default, the `initialPageParam` value specified in the endpoint will be used to make the first request, and then your `getNext/PreviousPageParam` callbacks will be used to calculate further page params as you fetch forwards or backwards.
159161

160162
If you want to start from a different page param, you may override the `initialPageParam` by passing it as part of the hook options:
161163

@@ -166,3 +168,82 @@ const { data } = useGetPokemonInfiniteQuery('fire', {
166168
```
167169

168170
The next and previous page params will still be calculated as needed.
171+
172+
### Frequently Used Query Hook Return Values
173+
174+
Infinite query hooks return [the same result object as normal query hooks](./queries.mdx#frequently-used-query-hook-return-values), but with [a few additional fields specific to infinite queries](../api/created-api/hooks.mdx#useinfinitequery-signature) and a different structure for `data` and `currentData`.
175+
176+
- `data` / `currentData`: These contain the same "latest successful" and "latest for current arg" results as normal queries, but the value is the `{pages, pageParams}` infinite query object with all fetched pages instead of a single response value.
177+
- `hasNextPage` / `hasPreviousPage`: When true, indicates that there _should_ be another page available to fetch in that direction. This is calculated by calling `getNext/PreviousPageParam` with the latest fetched pages.
178+
- `isFetchingNext/PreviousPage`: When true, indicates that the current `isFetching` flag represents a fetch in that direction.
179+
- `isFetchNext/PreviousPageError`: When true, indicates that the current `isError` flag represents an error for a failed fetch in that direction
180+
- `fetchNext/PreviousPage`: methods that will trigger a fetch for another page in that direction.
181+
182+
In most cases, you will probably read `data` and either `isLoading` or `isFetching` in order to render your UI. You will also want to use the `fetchNext/PreviousPage` methods to trigger fetching additional pages.
183+
184+
### Infinite Query Hook Usage Example
185+
186+
Here is an example of a typical infinite query endpoint definition, and hook usage in a component:
187+
188+
```tsx no-transpile
189+
type Pokemon = {
190+
id: string
191+
name: string
192+
}
193+
194+
const pokemonApi = createApi({
195+
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
196+
endpoints: (build) => ({
197+
getPokemon: build.infiniteQuery<Pokemon[], string, number>({
198+
infiniteQueryOptions: {
199+
initialPageParam: 0,
200+
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
201+
lastPageParam + 1,
202+
},
203+
query(pageParam) {
204+
return `https://example.com/listItems?page=${pageParam}`
205+
},
206+
}),
207+
}),
208+
})
209+
210+
function PokemonList({ pokemonType }: { pokemonType: string }) {
211+
const { data, isFetching, fetchNextPage, fetchPreviousPage, refetch } =
212+
pokemonApi.useGetPokemonInfiniteQuery(pokemonType)
213+
214+
const handleNextPage = async () => {
215+
await fetchNextPage()
216+
}
217+
218+
const handleRefetch = async () => {
219+
await refetch()
220+
}
221+
222+
const allResults = data?.pages.flat() ?? []
223+
224+
return (
225+
<div>
226+
<div>Type: {pokemonType}</div>
227+
<div>
228+
{allResults.map((pokemon, i: number | null | undefined) => (
229+
<div key={i}>{pokemon.name}</div>
230+
))}
231+
</div>
232+
<button onClick={() => handleNextPage()}>Fetch More</button>
233+
<button onClick={() => handleRefetch()}>Refetch</button>
234+
</div>
235+
)
236+
}
237+
```
238+
239+
In this example, the server returns an array of Pokemon as the response for each individual page. This component shows the results as a single list. Since the `data` field itself has a `pages` array of all responses, the component needs to flatten the pages into a single array to render that list. Alternately, it could map over the pages and show them in a paginated format.
240+
241+
Similarly, this example relies on manual user clicks on a "Fetch More" button to trigger fetching the next page, but could automatically call `fetchNextPage` based on things like an `IntersectionObserver`, a list component triggering some kind of "end of the list" event, or other similar indicators.
242+
243+
The endpoint itself only defines `getNextPageParam`, so this example doesn't support fetching backwards, but that can be provided in cases where backwards fetching makes sense. The page param here is a simple incremented number, but the page param
244+
245+
## Limiting Cache Entry Size
246+
247+
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.
248+
249+
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]`.

docs/rtk-query/usage/queries.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ description: 'RTK Query > Usage > Queries: fetching data from a server'
1212

1313
## Overview
1414

15-
This is the most common use case for RTK Query. A query operation can be performed with any data fetching library of your choice, but the general recommendation is that you only use queries for requests that retrieve data. For anything that alters data on the server or will possibly invalidate the cache, you should use a [Mutation](./mutations).
15+
Queries are operations that fetch data from the server and cache it within the client. This is the most common use case for RTK Query. A query operation can be performed with any data fetching library of your choice, but the general recommendation is that you only use queries for requests that retrieve data. For anything that alters data on the server or will possibly invalidate the cache, you should use a [Mutation](./mutations).
1616

17-
By default, RTK Query ships with [`fetchBaseQuery`](../api/fetchBaseQuery), which is a lightweight [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) wrapper that automatically handles request headers and response parsing in a manner similar to common libraries like `axios`. See [Customizing Queries](./customizing-queries) if `fetchBaseQuery` does not handle your requirements.
17+
A query can cache the status and result of any async/promise method. Since the most common type of query is an HTTP request, RTK Query ships with [`fetchBaseQuery`](../api/fetchBaseQuery), which is a lightweight [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) wrapper that automatically handles request headers and response parsing in a manner similar to common libraries like `axios`. See [Customizing Queries](./customizing-queries) if `fetchBaseQuery` does not handle your requirements.
1818

1919
:::info
2020

@@ -105,7 +105,7 @@ const api = createApi({
105105

106106
If you're using React Hooks, RTK Query does a few additional things for you. The primary benefit is that you get a render-optimized hook that allows you to have 'background fetching' as well as [derived booleans](#frequently-used-query-hook-return-values) for convenience.
107107

108-
Hooks are automatically generated based on the name of the `endpoint` in the service definition. An endpoint field with `getPost: builder.query()` will generate a hook named `useGetPostQuery`.
108+
Hooks are automatically generated based on the name of the `endpoint` in the service definition. An endpoint field with `getPost: builder.query()` will generate a hook named `useGetPostQuery`, as well as a generically-named hook attached to the endpoint, like `api.endpoints.getPost.useQuery`.
109109

110110
### Hook types
111111

packages/toolkit/src/query/tests/buildHooks.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1789,7 +1789,7 @@ describe('hooks tests', () => {
17891789
))}
17901790
</div>
17911791
<button data-testid="prevPage" onClick={() => handlePreviousPage()}>
1792-
nextPage
1792+
previousPage
17931793
</button>
17941794
<button data-testid="nextPage" onClick={() => handleNextPage()}>
17951795
nextPage

0 commit comments

Comments
 (0)