diff --git a/docs/rtk-query/api/createApi.mdx b/docs/rtk-query/api/createApi.mdx index 76a75de5f7..619a1bc783 100644 --- a/docs/rtk-query/api/createApi.mdx +++ b/docs/rtk-query/api/createApi.mdx @@ -39,8 +39,8 @@ import type { Pokemon } from './types' export const pokemonApi = createApi({ reducerPath: 'pokemonApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }), - endpoints: (builder) => ({ - getPokemonByName: builder.query({ + endpoints: (build) => ({ + getPokemonByName: build.query({ query: (name) => `pokemon/${name}`, }), }), @@ -148,6 +148,10 @@ See [Endpoint Definition Parameters](#endpoint-definition-parameters) for detail #### Query endpoint definition +Query endpoints (defined with `build.query()`) are used to cache data fetched from the server. + +You must specify either a `query` field (which will use the API's `baseQuery` to make a request), or a `queryFn` function with your own async logic. All other fields are optional. + ```ts title="Query endpoint definition" no-transpile export type QueryDefinition< QueryArg, @@ -220,8 +224,70 @@ export type QueryDefinition< } ``` +#### Infinite Query endpoint definition + +Infinite query endpoints (defined with `build.infiniteQuery()`) are used to cache multi-page data sets from the server. They have all the same callbacks and options as standard query endpoints, but also require an additional [`infiniteQueryOptions`](#infinitequeryoptions) field to specify how to calculate the unique parameters to fetch each page. + +For infinite query endpoints, there is a separation between the "query arg" used for the cache key, and the "page param" used to fetch a specific page. 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. This means the page param is what will be passed to your `query` or `queryFn` methods. + +```ts title="Infinite Query endpoint definition" no-transpile +export type PageParamFunction = ( + firstPage: DataType, + allPages: Array, + firstPageParam: PageParam, + allPageParams: Array, +) => PageParam | undefined | null + +export type InfiniteQueryDefinition< + QueryArg, + PageParam, + BaseQuery extends BaseQueryFn, + TagTypes extends string, + ResultType, + ReducerPath extends string = string, +> = + // Infinite queries have all the same options as query endpoints, + // but store the `{data, pages}` structure, and use the + // `PageParam` type as the `QueryArg` for fetching. + QueryDefinition> & { + /** + * Required options to configure the infinite query behavior. + * `initialPageParam` and `getNextPageParam` are required, to + * ensure the infinite query can properly fetch the next page of data. + * `initialPageparam` may be specified when using the + * endpoint, to override the default value. + */ + infiniteQueryOptions: { + /** + * The initial page parameter to use for the first page fetch. + */ + initialPageParam: PageParam + /** + * If specified, only keep this many pages in cache at once. + * If additional pages are fetched, older pages in the other + * direction will be dropped from the cache. + */ + maxPages?: number + /** + * This function can be set to automatically get the previous cursor for infinite queries. + * The result will also be used to determine the value of `hasPreviousPage`. + */ + getPreviousPageParam?: PageParamFunction + /** + * This function is required to automatically get the next cursor for infinite queries. + * The result will also be used to determine the value of `hasNextPage`. + */ + getNextPageParam: PageParamFunction + } + } +``` + #### Mutation endpoint definition +Mutation endpoints (defined with build.mutation()`) are used to send updates to the server, and force invalidation and refetching of query endpoints. + +As with queries, you must specify either the `query` option or the `queryFn` async method. + ```ts title="Mutation endpoint definition" no-transpile export type MutationDefinition< QueryArg, @@ -446,7 +512,7 @@ export interface BaseQueryApi { } ``` -#### queryFn function arguments +#### `queryFn` function arguments - `args` - The argument provided when the query itself is called - `api` - The `BaseQueryApi` object, containing `signal`, `dispatch` and `getState` properties @@ -458,6 +524,24 @@ export interface BaseQueryApi { [examples](docblock://query/endpointDefinitions.ts?token=EndpointDefinitionWithQueryFn.queryFn) +### `infiniteQueryOptions` + +_(only for `infiniteQuery` endpoints)_ + +[summary](docblock://query/endpointDefinitions.ts?token=InfiniteQueryExtraOptions.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. + +[examples](docblock://query/endpointDefinitions.ts?token=InfiniteQueryExtraOptions.infiniteQueryOptions) + ### `transformResponse` _(optional, not applicable with `queryFn`)_ diff --git a/docs/rtk-query/api/created-api/hooks.mdx b/docs/rtk-query/api/created-api/hooks.mdx index b0d5b12325..467296dbbf 100644 --- a/docs/rtk-query/api/created-api/hooks.mdx +++ b/docs/rtk-query/api/created-api/hooks.mdx @@ -19,9 +19,11 @@ However, RTK Query also provides the ability to auto-generate React hooks for ea import { createApi } from '@reduxjs/toolkit/query/react' ``` -If you have used the React-specific version of `createApi`, the generated `Api` slice structure will also contain a set of React hooks. The primary endpoint hooks are available as `api.endpoints[endpointName].useQuery` or `api.endpoints[endpointName].useMutation`, matching how you defined that endpoint. +If you have used the React-specific version of `createApi`, the generated `api` slice structure will also contain a set of React hooks. The primary endpoint hooks are available as `api.endpoints[endpointName].useQuery` or `api.endpoints[endpointName].useMutation`, matching how you defined that endpoint. -The same hooks are also added to the `Api` object itself, and given auto-generated names based on the endpoint name and query/mutation type. +### Generated Hook Names + +The same hooks are also added to the `api` object itself, and given auto-generated names based on the endpoint name and query/mutation type. For example, if you had endpoints for `getPosts` and `updatePost`, these options would be available: @@ -37,15 +39,27 @@ const [updatePost, { data }] = api.useUpdatePostMutation() The general format is `use(Endpointname)(Query|Mutation)` - `use` is prefixed, the first letter of your endpoint name is capitalized, then `Query` or `Mutation` is appended depending on the type. -RTK Query provides additional hooks for more advanced use-cases, although not all are generated directly on the `Api` object as well. The full list of hooks generated in the React-specific version of `createApi` is as follows: +### Available Hooks + +RTK Query provides additional hooks for more advanced use-cases, although not all are generated directly on the `api` object as well. + +Most of the hooks are generated on a per-endpoint basis. + +The full list of hooks generated in the React-specific version of `createApi` is as follows: -- [`useQuery`](#usequery) (endpoint-specific, also generated on the `Api` object) -- [`useMutation`](#usemutation) (endpoint-specific, also generated on the `Api` object) -- [`useQueryState`](#usequerystate) (endpoint-specific) -- [`useQuerySubscription`](#usequerysubscription) (endpoint-specific) -- [`useLazyQuery`](#uselazyquery) (endpoint-specific, also generated on the `Api` object) -- [`useLazyQuerySubscription`](#uselazyquerysubscription) (endpoint-specific) -- [`usePrefetch`](#useprefetch) (endpoint-agnostic) +- Endpoint-specific, generated the `api` object with a unique name and on the endpoint object with a generic name: + - [`useQuery`](#usequery) (all standard queries) + - [`useMutation`](#usemutation) (all mutations) + - [`useInfiniteQuery`](#useinfinitequery) (only infinite queries) + - [`useLazyQuery`](#uselazyquery) (all standard queries) +- Endpoint-specific, only generated on the endpoint object with a generic name: + - [`useQueryState`](#usequerystate) + - [`useQuerySubscription](#usequerysubscription) + - [`useLazyQuerySubscription](#uselazyquerysubscription) + - [`useInfiniteQueryState`](#useinfinitequerystate) + - [`useInfiniteQuerySubscription](#useinfinitequerysubscription) +- Endpoint-agnostic, generated on the `api` object: + - [`usePrefetch`](#useprefetch) For the example above, the full set of generated hooks for the api would be like so: @@ -57,13 +71,24 @@ api.endpoints.getPosts.useQuerySubscription(arg, options) api.endpoints.getPosts.useLazyQuery(options) api.endpoints.getPosts.useLazyQuerySubscription(options) +/* hooks attached to the `getManyPosts` infinite query endpoint definition */ +api.endpoints.getManyPosts.useInfiniteQuery(arg, options) +api.endpoints.getManyPosts.useInfiniteQueryState(arg, options) +api.endpoints.getManyPosts.useInfiniteQuerySubscription(arg, options) + /* Hooks attached to the `updatePost` mutation endpoint definition */ api.endpoints.updatePost.useMutation(options) -/* Hooks attached to the `Api` object */ -api.useGetPostsQuery(arg, options) // same as api.endpoints.getPosts.useQuery -api.useLazyGetPostsQuery(options) // same as api.endpoints.getPosts.useLazyQuery -api.useUpdatePostMutation(options) // same as api.endpoints.updatePost.useMutation +/* Hooks attached to the `api` object */ +// same as api.endpoints.getPosts.useQuery +api.useGetPostsQuery(arg, options) +// same as api.endpoints.getPosts.useLazyQuery +api.useLazyGetPostsQuery(arg, options) +// same as api.endpoints.updatePost.useMutation +api.useUpdatePostMutation(arg, options) +// same as api.endpoints.getManyPosts.useInfiniteQuery +api.useGetManyPostsInfiniteQuery(arg, options) +// Generic, used for any endpoint api.usePrefetch(endpointName, options) ``` @@ -233,7 +258,11 @@ The provided hooks have a degree of feature overlap in order to provide options -## `useQuery` +## Primary Hooks + +These hooks are the main methods you will use to interact with RTK Query in your React components. They encapsulate all of logic and options needed for most data fetching and update use cases. + +### `useQuery` ```ts title="Accessing a useQuery hook" no-transpile const useQueryResult = api.endpoints.getPosts.useQuery(arg, options) @@ -241,7 +270,9 @@ const useQueryResult = api.endpoints.getPosts.useQuery(arg, options) const useQueryResult = api.useGetPostsQuery(arg, options) ``` -#### Signature +[summary](docblock://query/react/buildHooks.ts?token=UseQuery) + +#### `useQuery` Signature ```ts no-transpile type UseQuery = ( @@ -261,23 +292,39 @@ type UseQueryOptions = { type UseQueryResult = { // Base query state - originalArgs?: unknown // Arguments passed to the query - data?: T // The latest returned result regardless of hook arg, if present - currentData?: T // The latest returned result for the current hook arg, if present - error?: unknown // Error result if present - requestId?: string // A string generated by RTK Query - endpointName?: string // The name of the given endpoint for the query - startedTimeStamp?: number // Timestamp for when the query was initiated - fulfilledTimeStamp?: number // Timestamp for when the query was completed + + // Arguments passed to the query + originalArgs?: unknown + // The latest returned result regardless of hook arg, if present + data?: T + // The latest returned result for the current hook arg, if present + currentData?: T + // Error result if present + error?: unknown + // A string generated by RTK Query + requestId?: string + // The name of the given endpoint for the query + endpointName?: string + // Timestamp for when the query was initiated + startedTimeStamp?: number + // Timestamp for when the query was completed + fulfilledTimeStamp?: number // Derived request status booleans - isUninitialized: boolean // Query has not started yet. - isLoading: boolean // Query is currently loading for the first time. No data yet. - isFetching: boolean // Query is currently fetching, but might have data from an earlier request. - isSuccess: boolean // Query has data from a successful load. - isError: boolean // Query is currently in an "error" state. - refetch: () => QueryActionCreatorResult // A function to force refetch the query - returns a Promise with additional methods + // Query has not started yet. + isUninitialized: boolean + // Query is currently loading for the first time. No data yet. + isLoading: boolean + // Query is currently fetching, but might have data from an earlier request. + isFetching: boolean + // Query has data from a successful load. + isSuccess: boolean + // Query is currently in an "error" state. + isError: boolean + + // A function to force refetch the query - returns a Promise with additional methods + refetch: () => QueryActionCreatorResult } ``` @@ -288,17 +335,13 @@ type UseQueryResult = { - **Returns** - A query result object containing the current loading state, the actual data or error returned from the API call, metadata about the request, and a function to `refetch` the data. Can be customized with `selectFromResult` -#### Description - -[summary](docblock://query/react/buildHooks.ts?token=UseQuery) - #### `skipToken` [summary](docblock://query/core/buildSelectors.ts?token=skipToken) See also [Skipping queries with TypeScript using `skipToken`](../../usage-with-typescript.mdx#skipping-queries-with-typescript-using-skiptoken) -## `useMutation` +### `useMutation` ```ts title="Accessing a useMutation hook" no-transpile const useMutationResult = api.endpoints.updatePost.useMutation(options) @@ -306,7 +349,9 @@ const useMutationResult = api.endpoints.updatePost.useMutation(options) const useMutationResult = api.useUpdatePostMutation(options) ``` -#### Signature +[summary](docblock://query/react/buildHooks.ts?token=UseMutation) + +#### `useMutation` Signature ```ts no-transpile type UseMutation = ( @@ -331,20 +376,33 @@ type UseMutationTrigger = (arg: any) => Promise< type UseMutationResult = { // Base query state - originalArgs?: unknown // Arguments passed to the latest mutation call. Not available if using the `fixedCacheKey` option - data?: T // Returned result if present - error?: unknown // Error result if present - endpointName?: string // The name of the given endpoint for the mutation - fulfilledTimeStamp?: number // Timestamp for when the mutation was completed + + // Arguments passed to the latest mutation call. Not available if using the `fixedCacheKey` option + originalArgs?: unknown + // Returned result if present + data?: T + // Error result if present + error?: unknown + // The name of the given endpoint for the mutation + endpointName?: string + // Timestamp for when the mutation was completed + fulfilledTimeStamp?: number // Derived request status booleans - isUninitialized: boolean // Mutation has not been fired yet - isLoading: boolean // Mutation has been fired and is awaiting a response - isSuccess: boolean // Mutation has data from a successful call - isError: boolean // Mutation is currently in an "error" state - startedTimeStamp?: number // Timestamp for when the latest mutation was initiated - reset: () => void // A method to manually unsubscribe from the mutation call and reset the result to the uninitialized state + // Mutation has not been fired yet + isUninitialized: boolean + // Mutation has been fired and is awaiting a response + isLoading: boolean + // Mutation has data from a successful call + isSuccess: boolean + // Mutation is currently in an "error" state + isError: boolean + // Timestamp for when the latest mutation was initiated + startedTimeStamp?: number + + // A method to manually unsubscribe from the mutation call and reset the result to the uninitialized state + reset: () => void } ``` @@ -373,103 +431,110 @@ selectFromResult: () => ({}) - a `reset` method to reset the hook back to its original state and remove the current result from the cache - an `originalArgs` property that contains the argument passed to the last call of the `trigger` function. -#### Description - -[summary](docblock://query/react/buildHooks.ts?token=UseMutation) - -## `useQueryState` +### `useInfiniteQuery` ```ts title="Accessing a useQuery hook" no-transpile -const useQueryStateResult = api.endpoints.getPosts.useQueryState(arg, options) +const useQueryResult = api.endpoints.getManyPosts.useInfiniteQuery(arg, options) +// or +const useQueryResult = api.useGetManyPostsInfiniteQuery(arg, options) ``` -#### Signature +[summary](docblock://query/react/buildHooks.ts?token=UseInfiniteQuery) + +#### `useInfiniteQuery` Signature ```ts no-transpile -type UseQueryState = ( +type UseInfiniteQuery = ( arg: any | SkipToken, - options?: UseQueryStateOptions, -) => UseQueryStateResult | SelectedQueryStateResult - -type UseQueryStateOptions = { - skip?: boolean - selectFromResult?: (result: UseQueryStateDefaultResult) => any -} + options?: UseQueryOptions, +) => UseInfiniteQueryResult -type UseQueryStateResult = { - // Base query state - originalArgs?: unknown // Arguments passed to the query - data?: T // The latest returned result regardless of hook arg, if present - currentData?: T // The latest returned result for the current hook arg, if present - error?: unknown // Error result if present - requestId?: string // A string generated by RTK Query - endpointName?: string // The name of the given endpoint for the query - startedTimeStamp?: number // Timestamp for when the query was initiated - fulfilledTimeStamp?: number // Timestamp for when the query was completed - - isUninitialized: false // Query has not started yet. - isLoading: false // Query is currently loading for the first time. No data yet. - isFetching: false // Query is currently fetching, but might have data from an earlier request. - isSuccess: false // Query has data from a successful load. - isError: false // Query is currently in an "error" state. +type InfiniteData = { + pages: Array + pageParams: Array } -``` - -- **Parameters** - - - `arg`: The argument passed to the query defined in the endpoint. - You can also pass in `skipToken` here as an alternative way of skipping the selection, see [skipToken](#skiptoken) - - `options`: A set of options that control the return value for the hook - -- **Returns** - - A query result object containing the current loading state, the actual data or error returned from the API call and metadata about the request. Can be customized with `selectFromResult` - -#### Description - -[summary](docblock://query/react/buildHooks.ts?token=UseQueryState) -## `useQuerySubscription` - -```ts title="Accessing a useQuerySubscription hook" no-transpile -const { refetch } = api.endpoints.getPosts.useQuerySubscription(arg, options) -``` - -#### Signature - -```ts no-transpile -type UseQuerySubscription = ( - arg: any | SkipToken, - options?: UseQuerySubscriptionOptions, -) => UseQuerySubscriptionResult - -type UseQuerySubscriptionOptions = { - skip?: boolean - refetchOnMountOrArgChange?: boolean | number +type UseInfiniteQueryOptions = { pollingInterval?: number skipPollingIfUnfocused?: boolean refetchOnReconnect?: boolean refetchOnFocus?: boolean + skip?: boolean + refetchOnMountOrArgChange?: boolean | number + selectFromResult?: (result: UseQueryStateDefaultResult) => any + initialPageParam?: PageParam } -type UseQuerySubscriptionResult = { - refetch: () => void // A function to force refetch the query +type UseInfiniteQueryResult = { + // Base query state + + // Arguments passed to the query + originalArgs?: unknown + // The latest returned result regardless of hook arg, if present + data?: InfiniteData + // The latest returned result for the current hook arg, if present + currentData?: InfiniteData + // Error result if present + error?: unknown + // A string generated by RTK Query + requestId?: string + // The name of the given endpoint for the query + endpointName?: string + // Timestamp for when the query was initiated + startedTimeStamp?: number + // Timestamp for when the query was completed + fulfilledTimeStamp?: number + + // Derived request status booleans + + // Query has not started yet. + isUninitialized: boolean + // Query is currently loading for the first time. No data yet. + isLoading: boolean + // Query is currently fetching, but might have data from an earlier request. + isFetching: boolean + // Query has data from a successful load. + isSuccess: boolean + // Query is currently in an "error" state. + isError: boolean + + // Derived request status booleans for infinite query pages + + // There is another page available querying forwards + hasNextPage: boolean + // There is another page available querying backwards + hasPreviousPage: boolean + // The current in-progress fetch is for the next page + isFetchingNextPage: boolean + // The current in-progress fetch is for the previous page + isFetchingPreviousPage: boolean + // The current error occurred fetching the next page + isFetchNextPageError: boolean + // The current error occurred fetching the previous page + isFetchPreviousPageError: boolean + + // A function to force refetch the query - returns a Promise with additional methods + refetch: () => InfiniteQueryActionCreatorResult + + // Triggers a fetch for the next page, based on the current cache + fetchNextPage: () => InfiniteQueryActionCreatorResult + // Triggers a fetch for the previous page, based on the current cache + fetchPreviousPage: () => InfiniteQueryActionCreatorResult } ``` - **Parameters** - - - `arg`: The argument passed to the query defined in the endpoint. + - `arg`: The query argument to be used in constructing the query itself, and as a cache key for the query. You can also pass in `skipToken` here as an alternative way of skipping the query, see [skipToken](#skiptoken) - `options`: A set of options that control the fetching behavior of the hook - - **Returns** - - An object containing a function to `refetch` the data + - A query result object containing the current loading state, the actual data or error returned from the API call, metadata about the request, and a function to `refetch` the data. Can be customized with `selectFromResult` -#### Description +## Secondary Hooks -[summary](docblock://query/react/buildHooks.ts?token=UseQuerySubscription) +These hooks are useful for specific additional use cases in your application, but will probably not be used that frequently. -## `useLazyQuery` +### `useLazyQuery` ```ts title="Accessing a useLazyQuery hook" no-transpile const [trigger, result, lastPromiseInfo] = @@ -478,7 +543,9 @@ const [trigger, result, lastPromiseInfo] = const [trigger, result, lastPromiseInfo] = api.useLazyGetPostsQuery(options) ``` -#### Signature +[summary](docblock://query/react/buildHooks.ts?token=UseLazyQuery) + +#### `useLazyQuery` Signature ```ts no-transpile type UseLazyQuery = ( @@ -496,32 +563,57 @@ type UseLazyQueryOptions = { type UseLazyQueryTrigger = (arg: any, preferCacheValue?: boolean) => Promise< QueryResultSelectorResult > & { - arg: unknown // Whatever argument was provided to the query - requestId: string // A string generated by RTK Query - subscriptionOptions: SubscriptionOptions // The values used for the query subscription - abort: () => void // A method to cancel the query promise - unwrap: () => Promise // A method to unwrap the query call and provide the raw response/error - unsubscribe: () => void // A method used to manually unsubscribe from the query results - refetch: () => void // A method used to re-run the query. In most cases when using a lazy query, you will never use this and should prefer to call the trigger again. - updateSubscriptionOptions: (options: SubscriptionOptions) () => void // A method used to update the subscription options (eg. pollingInterval) + // Whatever argument was provided to the query + arg: unknown + // A string generated by RTK Query + requestId: string + // The values used for the query subscription + subscriptionOptions: SubscriptionOptions + + // A method to cancel the query promise + abort: () => void + // A method to unwrap the query call and provide the raw response/error + unwrap: () => Promise + // A method used to manually unsubscribe from the query results + unsubscribe: () => void + // A method used to re-run the query. In most cases when using a lazy query, you will never use this and should prefer to call the trigger again. + refetch: () => void + // A method used to update the subscription options (eg. pollingInterval) + updateSubscriptionOptions: (options: SubscriptionOptions) () => void } type UseQueryStateResult = { // Base query state - originalArgs?: unknown // Arguments passed to the query - data?: T // The latest returned result regardless of trigger arg, if present - currentData?: T // The latest returned result for the trigger arg, if present - error?: unknown // Error result if present - requestId?: string // A string generated by RTK Query - endpointName?: string // The name of the given endpoint for the query - startedTimeStamp?: number // Timestamp for when the query was initiated - fulfilledTimeStamp?: number // Timestamp for when the query was completed - - isUninitialized: false // Query has not started yet. - isLoading: false // Query is currently loading for the first time. No data yet. - isFetching: false // Query is currently fetching, but might have data from an earlier request. - isSuccess: false // Query has data from a successful load. - isError: false // Query is currently in an "error" state. + + // Arguments passed to the query + originalArgs?: unknown + // The latest returned result regardless of hook arg, if present + data?: T + // The latest returned result for the current hook arg, if present + currentData?: T + // Error result if present + error?: unknown + // A string generated by RTK Query + requestId?: string + // The name of the given endpoint for the query + endpointName?: string + // Timestamp for when the query was initiated + startedTimeStamp?: number + // Timestamp for when the query was completed + fulfilledTimeStamp?: number + + // Derived request status booleans + + // Query has not started yet. + isUninitialized: boolean + // Query is currently loading for the first time. No data yet. + isLoading: boolean + // Query is currently fetching, but might have data from an earlier request. + isFetching: boolean + // Query has data from a successful load. + isSuccess: boolean + // Query is currently in an "error" state. + isError: boolean } type UseLazyQueryLastPromiseInfo = { @@ -538,56 +630,19 @@ type UseLazyQueryLastPromiseInfo = { - `result`: A query result object containing the current loading state, the actual data or error returned from the API call and metadata about the request. Can be customized with `selectFromResult` - `lastPromiseInfo`: An object containing the last argument used to call the trigger function -#### Description +### `usePrefetch` -[summary](docblock://query/react/buildHooks.ts?token=UseLazyQuery) - -## `useLazyQuerySubscription` - -```ts title="Accessing a useLazyQuerySubscription hook" no-transpile -const [trigger, lastArg] = - api.endpoints.getPosts.useLazyQuerySubscription(options) -``` - -#### Signature - -```ts no-transpile -type UseLazyQuerySubscription = ( - options?: UseLazyQuerySubscriptionOptions, -) => [UseLazyQuerySubscriptionTrigger, LastArg] - -type UseLazyQuerySubscriptionOptions = { - pollingInterval?: number - skipPollingIfUnfocused?: boolean - refetchOnReconnect?: boolean - refetchOnFocus?: boolean -} - -type UseLazyQuerySubscriptionTrigger = ( - arg: any, - preferCacheValue?: boolean, -) => void +```ts title="Accessing a usePrefetch hook" no-transpile +const prefetchCallback = api.usePrefetch(endpointName, options) ``` -- **Parameters** - - - `options`: A set of options that control the fetching behavior of the hook. The options will only have an effect after the lazy query has been triggered at least once. - -- **Returns**: A tuple containing: - - `trigger`: A function that fetches the corresponding data for the endpoint when called - - `lastArg`: The last argument used to call the trigger function - -#### Description - -[summary](docblock://query/react/buildHooks.ts?token=UseLazyQuerySubscription) +A React hook which can be used to initiate fetching data ahead of time. -## `usePrefetch` +##### Features -```ts title="Accessing a usePrefetch hook" no-transpile -const prefetchCallback = api.usePrefetch(endpointName, options) -``` +- Manual control over firing a request to retrieve data -#### Signature +##### Signature ```ts no-transpile type UsePrefetch = ( @@ -618,10 +673,165 @@ type PrefetchCallback = (arg: any, options?: UsePrefetchOptions) => void - **Returns** - A `prefetch` callback that when called, will initiate fetching the data for the provided endpoint -#### Description +## Implementation Hooks -A React hook which can be used to initiate fetching data ahead of time. +This hooks exist as implementation details of the primary hooks. They may be useful in rare cases, but you should generally use the primary hooks in your apps. -#### Features +### `useQueryState` -- Manual control over firing a request to retrieve data +```ts title="Accessing a useQuery hook" no-transpile +const useQueryStateResult = api.endpoints.getPosts.useQueryState(arg, options) +``` + +[summary](docblock://query/react/buildHooks.ts?token=UseQueryState) + +##### `useQueryState` Signature + +```ts no-transpile +type UseQueryState = ( + arg: any | SkipToken, + options?: UseQueryStateOptions, +) => UseQueryStateResult | SelectedQueryStateResult + +type UseQueryStateOptions = { + skip?: boolean + selectFromResult?: (result: UseQueryStateDefaultResult) => any +} + +type UseQueryStateResult = { + // Base query state + + // Arguments passed to the query + originalArgs?: unknown + // The latest returned result regardless of hook arg, if present + data?: T + // The latest returned result for the current hook arg, if present + currentData?: T + // Error result if present + error?: unknown + // A string generated by RTK Query + requestId?: string + // The name of the given endpoint for the query + endpointName?: string + // Timestamp for when the query was initiated + startedTimeStamp?: number + // Timestamp for when the query was completed + fulfilledTimeStamp?: number + + // Derived request status booleans + + // Query has not started yet. + isUninitialized: boolean + // Query is currently loading for the first time. No data yet. + isLoading: boolean + // Query is currently fetching, but might have data from an earlier request. + isFetching: boolean + // Query has data from a successful load. + isSuccess: boolean + // Query is currently in an "error" state. + isError: boolean +} +``` + +- **Parameters** + + - `arg`: The argument passed to the query defined in the endpoint. + You can also pass in `skipToken` here as an alternative way of skipping the selection, see [skipToken](#skiptoken) + - `options`: A set of options that control the return value for the hook + +- **Returns** + - A query result object containing the current loading state, the actual data or error returned from the API call and metadata about the request. Can be customized with `selectFromResult` + +### `useQuerySubscription` + +```ts title="Accessing a useQuerySubscription hook" no-transpile +const { refetch } = api.endpoints.getPosts.useQuerySubscription(arg, options) +``` + +[summary](docblock://query/react/buildHooks.ts?token=UseQuerySubscription) + +##### `useQuerySubscription` Signature + +```ts no-transpile +type UseQuerySubscription = ( + arg: any | SkipToken, + options?: UseQuerySubscriptionOptions, +) => UseQuerySubscriptionResult + +type UseQuerySubscriptionOptions = { + skip?: boolean + refetchOnMountOrArgChange?: boolean | number + pollingInterval?: number + skipPollingIfUnfocused?: boolean + refetchOnReconnect?: boolean + refetchOnFocus?: boolean +} + +type UseQuerySubscriptionResult = { + refetch: () => void // A function to force refetch the query +} +``` + +- **Parameters** + + - `arg`: The argument passed to the query defined in the endpoint. + You can also pass in `skipToken` here as an alternative way of skipping the query, see [skipToken](#skiptoken) + - `options`: A set of options that control the fetching behavior of the hook + +- **Returns** + - An object containing a function to `refetch` the data + +### `useInfiniteQueryState` + +```ts title="Accessing a useInfiniteQueryState hook" no-transpile +const useInfiniteQueryStateResult = + api.endpoints.getManyPosts.useInfiniteQueryState(arg, options) +``` + +[summary](docblock://query/react/buildHooks.ts?token=UseInfiniteQueryState) + +### `useInfiniteQuerySubscription` + +```ts title="Accessing a useInfiniteQuerySubscription hook" no-transpile +const useInfiniteQuerySubscriptionResult = + api.endpoints.getManyPosts.useInfiniteQuerySubscription(arg, options) +``` + +[summary](docblock://query/react/buildHooks.ts?token=UseInfiniteQuerySubscription) + +### `useLazyQuerySubscription` + +```ts title="Accessing a useLazyQuerySubscription hook" no-transpile +const [trigger, lastArg] = + api.endpoints.getPosts.useLazyQuerySubscription(options) +``` + +[summary](docblock://query/react/buildHooks.ts?token=UseLazyQuerySubscription) + +##### `useLazyQuerySubscription` Signature + +```ts no-transpile +type UseLazyQuerySubscription = ( + options?: UseLazyQuerySubscriptionOptions, +) => [UseLazyQuerySubscriptionTrigger, LastArg] + +type UseLazyQuerySubscriptionOptions = { + pollingInterval?: number + skipPollingIfUnfocused?: boolean + refetchOnReconnect?: boolean + refetchOnFocus?: boolean +} + +type UseLazyQuerySubscriptionTrigger = ( + arg: any, + preferCacheValue?: boolean, +) => void +``` + +- **Parameters** + + - `options`: A set of options that control the fetching behavior of the hook. The options will only have an effect after the lazy query has been triggered at least once. + +- **Returns**: A tuple containing: + - `trigger`: A function that fetches the corresponding data for the endpoint when called + - `lastArg`: The last argument used to call the trigger function diff --git a/docs/rtk-query/usage/infinite-queries.mdx b/docs/rtk-query/usage/infinite-queries.mdx new file mode 100644 index 0000000000..3b7335c591 --- /dev/null +++ b/docs/rtk-query/usage/infinite-queries.mdx @@ -0,0 +1,249 @@ +--- +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 a 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 the `query` or `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: 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. For a query like `useGetPokemonInfiniteQuery('fire')`, 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`, defining tags, 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`, 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 for a first fetch. `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 = ( + currentPage: DataType, + allPages: DataType[], + currentPageParam: PageParam, + allPageParams: PageParam[], +) => PageParam | undefined | null +``` + +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}`. + +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. + +The "current" arguments will be either the last page for `getNextPageParam`, or the first page for `getPreviousPageParam`. + +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({ + 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`, as well as a generically-named hook attached to the endpoint, like `api.endpoints.getPokemon.useInfiniteQuery`. + +### Hook Types + +There are 3 infinite query-related hooks: + +1. [`useInfiniteQuery`](../api/created-api/hooks.mdx#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`](../api/created-api/hooks.mdx#useinfinitequerysubscription) + - Returns a `refetch` function and `fetchNext/PreviousPage` functions, and accepts all hooks options. Automatically triggers refetches of data from an endpoint, and 'subscribes' the component to the cached data. +3. [`useInfiniteQueryState`](../api/created-api/hooks.mdx#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?)`. + +The `queryOptions` object accepts [all the same parameters as `useQuery`](./queries.mdx#query-hook-options), including `skip`, `selectFromResult`, and refetching/polling options. + +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. + +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. + +### Frequently Used Query Hook Return Values + +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`. + +- `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. +- `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. +- `isFetchingNext/PreviousPage`: When true, indicates that the current `isFetching` flag represents a fetch in that direction. +- `isFetchNext/PreviousPageError`: When true, indicates that the current `isError` flag represents an error for a failed fetch in that direction +- `fetchNext/PreviousPage`: methods that will trigger a fetch for another page in that direction. + +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. + +### Infinite Query Hook Usage Example + +Here is an example of a typical infinite query endpoint definition, and hook usage in a component: + +```tsx no-transpile +type Pokemon = { + id: string + name: string +} + +const pokemonApi = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }), + endpoints: (build) => ({ + getPokemon: build.infiniteQuery({ + infiniteQueryOptions: { + initialPageParam: 0, + getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => + lastPageParam + 1, + }, + query(pageParam) { + return `https://example.com/listItems?page=${pageParam}` + }, + }), + }), +}) + +function PokemonList({ pokemonType }: { pokemonType: string }) { + const { data, isFetching, fetchNextPage, fetchPreviousPage, refetch } = + pokemonApi.useGetPokemonInfiniteQuery(pokemonType) + + const handleNextPage = async () => { + await fetchNextPage() + } + + const handleRefetch = async () => { + await refetch() + } + + const allResults = data?.pages.flat() ?? [] + + return ( +
+
Type: {pokemonType}
+
+ {allResults.map((pokemon, i: number | null | undefined) => ( +
{pokemon.name}
+ ))} +
+ + +
+ ) +} +``` + +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. + +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. + +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 + +## Limiting Cache Entry Size + +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. + +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]`. diff --git a/docs/rtk-query/usage/queries.mdx b/docs/rtk-query/usage/queries.mdx index 970a3996b5..91775607f8 100644 --- a/docs/rtk-query/usage/queries.mdx +++ b/docs/rtk-query/usage/queries.mdx @@ -12,9 +12,9 @@ description: 'RTK Query > Usage > Queries: fetching data from a server' ## Overview -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). +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). -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. +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. :::info @@ -105,7 +105,7 @@ const api = createApi({ 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. -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`. +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`. ### Hook types diff --git a/packages/toolkit/src/query/core/apiState.ts b/packages/toolkit/src/query/core/apiState.ts index 307706ded9..53f1becba5 100644 --- a/packages/toolkit/src/query/core/apiState.ts +++ b/packages/toolkit/src/query/core/apiState.ts @@ -38,7 +38,15 @@ export type PageParamFunction = ( ) => PageParam | undefined | null export type InfiniteQueryConfigOptions = { + /** + * The initial page parameter to use for the first page fetch. + */ initialPageParam: PageParam + /** + * If specified, only keep this many pages in cache at once. + * If additional pages are fetched, older pages in the other + * direction will be dropped from the cache. + */ maxPages?: number /** * This function can be set to automatically get the previous cursor for infinite queries. diff --git a/packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts b/packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts index e2b91f7c1f..d23370bc11 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts @@ -248,12 +248,12 @@ export type MutationLifecycleApi< * baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }), * reducerPath: 'postsApi', * tagTypes: ['Posts'], - * endpoints: (builder) => ({ - * getPosts: builder.query({ + * endpoints: (build) => ({ + * getPosts: build.query({ * query: () => `/posts`, * }), * - * getPostById: builder.query({ + * getPostById: build.query({ * query: (postId) => `/posts/${postId}`, * }), * }), @@ -283,8 +283,8 @@ export type MutationLifecycleApi< * } * * export const extendedApiSlice = baseApiSlice.injectEndpoints({ - * endpoints: (builder) => ({ - * getPostsByUserId: builder.query({ + * endpoints: (build) => ({ + * getPostsByUserId: build.query({ * query: (userId) => `/posts/user/${userId}`, * * onQueryStarted: updatePostOnFulfilled, @@ -346,12 +346,12 @@ export type TypedQueryOnQueryStarted< * baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }), * reducerPath: 'postsApi', * tagTypes: ['Posts'], - * endpoints: (builder) => ({ - * getPosts: builder.query({ + * endpoints: (build) => ({ + * getPosts: build.query({ * query: () => `/posts`, * }), * - * getPostById: builder.query({ + * getPostById: build.query({ * query: (postId) => `/posts/${postId}`, * }), * }), @@ -377,8 +377,8 @@ export type TypedQueryOnQueryStarted< * } * * export const extendedApiSlice = baseApiSlice.injectEndpoints({ - * endpoints: (builder) => ({ - * addPost: builder.mutation>({ + * endpoints: (build) => ({ + * addPost: build.mutation>({ * query: (body) => ({ * url: `posts/add`, * method: 'POST', @@ -388,7 +388,7 @@ export type TypedQueryOnQueryStarted< * onQueryStarted: updatePostOnFulfilled, * }), * - * updatePost: builder.mutation({ + * updatePost: build.mutation({ * query: ({ id, ...patch }) => ({ * url: `post/${id}`, * method: 'PATCH', diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index fa7a5ebcbc..9112baffc8 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -98,7 +98,7 @@ export interface CreateApiOptions< */ serializeQueryArgs?: SerializeQueryArgs /** - * Endpoints are just a set of operations that you want to perform against your server. You define them as an object using the builder syntax. There are two basic endpoint types: [`query`](../../rtk-query/usage/queries) and [`mutation`](../../rtk-query/usage/mutations). + * Endpoints are a set of operations that you want to perform against your server. You define them as an object using the builder syntax. There are three endpoint types: [`query`](../../rtk-query/usage/queries), [`infiniteQuery`](../../rtk-query/usage/infinite-queries) and [`mutation`](../../rtk-query/usage/mutations). */ endpoints( build: EndpointBuilder, diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index d9bac76afd..121009a709 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -607,6 +607,51 @@ export interface InfiniteQueryExtraOptions< */ invalidatesTags?: never + /** + * Required options to configure the infinite query behavior. + * `initialPageParam` and `getNextPageParam` are required, to + * ensure the infinite query can properly fetch the next page of data. + * `initialPageParam` may be specified when using the + * endpoint, to override the default value. + * + * @example + * + * ```ts + * // codeblock-meta title="infiniteQueryOptions example" + * import { createApi, fetchBaseQuery, defaultSerializeQueryArgs } from '@reduxjs/toolkit/query/react' + * + * type Pokemon = { + * id: string + * name: string + * } + * + const pokemonApi = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }), + endpoints: (build) => ({ + getInfinitePokemonWithMax: build.infiniteQuery({ + 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}` + }, + }), + }), + }) + + * ``` + */ infiniteQueryOptions: InfiniteQueryConfigOptions /** @@ -691,7 +736,7 @@ export type InfiniteQueryDefinition< ResultType, ReducerPath extends string = string, > = - // Intentionally use `PageParam` as the QueryArg` type + // Intentionally use `PageParam` as the `QueryArg` type BaseEndpointDefinition & InfiniteQueryExtraOptions< TagTypes, diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 9923b23990..184df19009 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -65,7 +65,7 @@ import { useStableQueryArgs } from './useSerializedStableValue' import { useShallowStableValue } from './useShallowStableValue' import type { InfiniteQueryDirection } from '../core/apiState' import { isInfiniteQueryDefinition } from '../endpointDefinitions' -import { StartInfiniteQueryActionCreator } from '../core/buildInitiate' +import type { StartInfiniteQueryActionCreator } from '../core/buildInitiate' // Copy-pasted from React-Redux const canUseDOM = () => @@ -890,6 +890,31 @@ export type InfiniteQueryStateSelector< D extends InfiniteQueryDefinition, > = (state: UseInfiniteQueryStateDefaultResult) => R +/** + * A React hook that 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. The component will re-render as the loading status changes and the data becomes available. Additionally, it will cache multiple "pages" worth of responses within a single cache entry, and allows fetching more pages forwards and backwards from the current cached pages. + * + * The query arg is used as a cache key. Changing the query arg will tell the hook to re-fetch the data if it does not exist in the cache already, and the hook will return the data for that query arg once it's available. + * + * The `data` field will be a `{pages: Data[], pageParams: PageParam[]}` structure containing all fetched page responses and the corresponding page param values for each page. You may use this to render individual pages, combine all pages into a single infinite list, or other display logic as needed. + * + * This hook combines the functionality of both [`useInfiniteQueryState`](#useinfinitequerystate) and [`useInfiniteQuerySubscription`](#useinfinitequerysubscription) together, and is intended to be used in the majority of situations. + * + * As with normal query hooks, `skipToken` is a valid argument that will skip the query from executing. + * + * By default, the initial request will use the `initialPageParam` value that was defined on the infinite query endpoint. If you want to start from a different value, you can pass `initialPageParam` as part of the hook options to override that initial request value. + * + * Use the returned `fetchNextPage` and `fetchPreviousPage` methods on the hook result object to trigger fetches forwards and backwards. These will always calculate the next or previous page param based on the current cached pages and the provided `getNext/PreviousPageParam` callbacks defined in the endpoint. + * + * + * #### Features + * + * - Automatically triggers requests to retrieve data based on the hook argument and whether cached data exists by default + * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts + * - Caches multiple pages worth of responses, and provides methods to trigger more page fetches forwards and backwards + * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met + * - Returns the latest request status and cached data from the Redux store + * - Re-renders as the request status changes and data becomes available + */ export type UseInfiniteQuery< D extends InfiniteQueryDefinition, > = = UseInfiniteQueryStateDefaultResult>( @@ -902,6 +927,16 @@ export type UseInfiniteQuery< 'fetchNextPage' | 'fetchPreviousPage' > +/** + * A React hook that reads the request status and cached data from the Redux store. The component will re-render as the loading status changes and the data becomes available. + * + * Note that this hook does not trigger fetching new data. For that use-case, see [`useInfiniteQuery`](#useinfinitequery) or [`useInfiniteQuerySubscription`](#useinfinitequerysubscription). + * + * #### Features + * + * - Returns the latest request status and cached data from the Redux store + * - Re-renders as the request status changes and data becomes available + */ export type UseInfiniteQueryState< D extends InfiniteQueryDefinition, > = = UseInfiniteQueryStateDefaultResult>( @@ -925,6 +960,20 @@ export type TypedUseInfiniteQueryState< > > +/** + * A React hook that automatically triggers fetches of data from an endpoint, and 'subscribes' the component to the cached data. Additionally, it will cache multiple "pages" worth of responses within a single cache entry, and allows fetching more pages forwards and backwards from the current cached pages. + * + * The query arg is used as a cache key. Changing the query arg will tell the hook to re-fetch the data if it does not exist in the cache already. + * + * Note that this hook does not return a request status or cached data. For that use-case, see [`useInfiniteQuery`](#useinfinitequery) or [`useInfiniteQueryState`](#useinfinitequerystate). + * + * #### Features + * + * - Automatically triggers requests to retrieve data based on the hook argument and whether cached data exists by default + * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts + * - Caches multiple pages worth of responses, and provides methods to trigger more page fetches forwards and backwards + * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met + */ export type UseInfiniteQuerySubscription< D extends InfiniteQueryDefinition, > = ( diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index a9535aca52..d6ad241df1 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -1789,7 +1789,7 @@ describe('hooks tests', () => { ))}