-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7a2ee50
commit 40568c1
Showing
3 changed files
with
175 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
--- | ||
id: infinite-queries | ||
title: Infinite Queries | ||
sidebar_label: Infinite Queries | ||
hide_title: true | ||
description: 'RTK Query > Usage > Infinite Queries: fetching many data pages from a server' | ||
--- | ||
|
||
| ||
|
||
# Infinite Queries | ||
|
||
## Overview | ||
|
||
Rendering lists that can additively "load more" data onto an existing set of data or "infinite scroll" is also a very common UI pattern. | ||
|
||
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. | ||
|
||
## Infinite Query Concepts | ||
|
||
RTK Query's support for infinite queries is modeled after [React Query's infinite query API design](https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries). | ||
|
||
### Query Args, Page Params, and Cache Structure | ||
|
||
With standard query endpoints: | ||
|
||
- 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 | ||
- The query arg is also serialized to generate the unique internal key for this specific cache entry | ||
- The single response value is directly stored as the `data` field in the cache entry | ||
|
||
Infinite queries work similarly, but have a couple additional layers: | ||
|
||
- You still specify a "query arg", which is still used to generate the unique cache key for this specific cache entry | ||
- 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. | ||
- 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. | ||
|
||
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: | ||
|
||
```ts no-transpile | ||
{ | ||
queries: { | ||
"getPokemon('fire')": { | ||
data: { | ||
pages: [ | ||
["Charmander", "Charmeleon"], | ||
["Charizard", "Vulpix"], | ||
["Magmar", "Flareon"] | ||
], | ||
pageParams: [ | ||
1, | ||
2, | ||
3 | ||
] | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
This structure allows flexibility in how your UI chooses to render the data (showing individual pages, flattening into a single list), enables limiting how many pages are kept in cache, and makes it possible to dynamically determine the next or previous page to fetch based on either the data or the page params. | ||
|
||
## Defining Infinite Query Endpoints | ||
|
||
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. | ||
|
||
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. | ||
|
||
### `infiniteQueryOptions` | ||
|
||
The `infiniteQueryOptions` field includes: | ||
|
||
- `initialPageParam`: the default page param value used for the first request, if this was not specified at the usage site | ||
- `maxPages`: an optional limit to how many fetched pages will be kept in the cache entry at a time | ||
- `getNextPageParam`: a required callback you must provide to calculate the next page param, given the existing cached pages and page params | ||
- `getPreviousPageParam`: an optional callback that will be used to calculate the previous page param, if you try to fetch backwards. | ||
|
||
Both `initialPageParam` and `getNextPageParam` are required, to | ||
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. | ||
|
||
### Page Param Functions | ||
|
||
`getNextPageParam` and `getPreviousPageParam` are user-defined, giving you flexibility to determine how those values are calculated: | ||
|
||
```ts | ||
export type PageParamFunction<DataType, PageParam> = ( | ||
currentPage: DataType, | ||
allPages: Array<DataType>, | ||
currentPageParam: PageParam, | ||
allPageParams: Array<PageParam>, | ||
) => PageParam | undefined | null | ||
``` | ||
This enables a number of possible infinite query use cases, including cursor-based and limit+offset-based queries. | ||
The "current" arguments will be either the last page for `getNext`, or the first page for `getPrevious`. | ||
If there is no possible page to fetch in that direction, the callback should return `undefined`. | ||
### Infinite Query Definition Example | ||
A complete example of this might look like: | ||
```ts no-transpile title="Infinite Query definition example" | ||
type Pokemon = { | ||
id: string | ||
name: string | ||
} | ||
|
||
const pokemonApi = createApi({ | ||
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }), | ||
endpoints: (build) => ({ | ||
getInfinitePokemonWithMax: build.infiniteQuery<Pokemon[], string, number>({ | ||
infiniteQueryOptions: { | ||
initialPageParam: 0, | ||
maxPages: 3, | ||
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => | ||
lastPageParam + 1, | ||
getPreviousPageParam: ( | ||
firstPage, | ||
allPages, | ||
firstPageParam, | ||
allPageParams, | ||
) => { | ||
return firstPageParam > 0 ? firstPageParam - 1 : undefined | ||
}, | ||
}, | ||
query(pageParam) { | ||
return `https://example.com/listItems?page=${pageParam}` | ||
}, | ||
}), | ||
}), | ||
}) | ||
``` | ||
|
||
## Performing Infinite Queries with React Hooks | ||
|
||
[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`. | ||
|
||
### Hook Types | ||
|
||
There are 3 infinite query-related hooks: | ||
|
||
1. [`useInfiniteQuery`]() | ||
|
||
- Composes `useInfiniteQuerySubscription` and `useInfiniteQueryState`, and is the primary hook. Automatically triggers fetches of data from an endpoint, 'subscribes' the component to the cached data, and reads the request status and cached data from the Redux store. | ||
|
||
2. [`useInfiniteQuerySubscription`]() | ||
|
||
- Returns a `refetch` function and accepts all hooks options. Automatically triggers refetches of data from an endpoint, and 'subscribes' the component to the cached data. | ||
|
||
3. [`useInfiniteQueryState`]() | ||
|
||
- Returns the query state and accepts `skip` and `selectFromResult`. Reads the request status and cached data from the Redux store. | ||
|
||
In practice, the standard `useInfiniteQuery`-based hooks such as `useGetPokemonInfiniteQuery` will be the primary hooks used in your application, but the other hooks are available for specific use cases. | ||
|
||
### Query Hook Options | ||
|
||
The query hooks expect two parameters: `(queryArg?, queryOptions?)`. | ||
|
||
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. | ||
|
||
If you want to start from a different page param, you may override the `initialPageParam` by passing it as part of the hook options: | ||
|
||
```ts no-transpile | ||
const { data } = useGetPokemonInfiniteQuery('fire', { | ||
initialPageParam: 3, | ||
}) | ||
``` | ||
|
||
The next and previous page params will still be calculated as needed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters