new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Redux Toolkit · RTK Query · all subjects

createapi/endpoints/infinitequery

31 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Infinite Query endpoint definition

Infinite query endpoints (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 infiniteQueryOptions field to specify how to calculate unique parameters to fetch each page. For infinite queries, there is separation between the query arg used for the cache key and the page param used to fetch a specific page. The query and queryFn methods receive a combined {queryArg, pageParam} object as the argument.

useInfiniteQuery hook signature and parameters

useInfiniteQuery accepts arg (query argument or skipToken) and options (UseInfiniteQueryOptions). UseInfiniteQueryOptions include pollingInterval, skipPollingIfUnfocused, refetchOnReconnect, refetchOnFocus, skip, refetchOnMountOrArgChange, selectFromResult, initialPageParam, and refetchCachedPages. Returns UseInfiniteQueryResult.

useInfiniteQuery return object properties

UseInfiniteQueryResult<Data, PageParam> contains: originalArgs, data (InfiniteData with pages array and pageParams array), currentData, error, requestId, endpointName, startedTimeStamp, fulfilledTimeStamp, isUninitialized, isLoading, isFetching, isSuccess, isError, hasNextPage (another page available querying forwards), hasPreviousPage (another page available querying backwards), isFetchingNextPage, isFetchingPreviousPage, isFetchNextPageError, isFetchPreviousPageError, refetch (function accepting optional refetchCachedPages), fetchNextPage (triggers next page fetch), and fetchPreviousPage (triggers previous page fetch).

InfiniteData structure

InfiniteData<Data, PageParam> is an object with two properties: pages (array of Data) and pageParams (array of PageParam). This structure holds accumulated pages and their corresponding page parameters for infinite queries.

useInfiniteQueryState hook purpose

useInfiniteQueryState is an implementation hook that returns the state for an infinite query without automatically triggering requests or subscribing the component to updates.

useInfiniteQuerySubscription hook purpose

useInfiniteQuerySubscription is an implementation hook that subscribes a component to infinite query state and manages fetching behavior, without returning full query state.

RTK Query supports infinite scrolling

RTK Query supports infinite scrolling through the infiniteQuery endpoint type.

Infinite queries overview and use case

Infinite Query endpoints in RTK Query support rendering lists that additively load more data or infinite scroll. They are similar to standard query endpoints in that they fetch data and cache results, but they have the ability to fetch next and previous pages, and contain all related fetched pages in a single cache entry.

Query arg vs page param separation in infinite queries

In infinite queries, there is a separation between the query arg (used to generate the unique cache key) and the page param (used to fetch a specific page). The query and queryFn methods receive a combined object {queryArg, pageParam} as the first argument, instead of just the queryArg by itself.

Infinite query cache entry structure

The data field in an infinite query cache entry stores a {pages: DataType[], pageParams: PageParam[]} structure that contains all fetched page results and their corresponding page params used to fetch them.

Infinite query definition with build.infiniteQuery()

Infinite query endpoints are defined by returning an object inside the endpoints section of createApi using the build.infiniteQuery() method. They are an extension of standard query endpoints and can specify the same options as standard queries (providing query or queryFn, customizing with transformResponse, lifecycles with onCacheEntryAdded and onQueryStarted, defining tags, etc). They also require an additional infiniteQueryOptions field to specify the infinite query behavior.

infiniteQuery generics for TypeScript

With TypeScript, you must supply 3 generic arguments to 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 used to request a specific page. If there is no argument, use void for the arg type instead.

infiniteQueryOptions required and optional fields

The infiniteQueryOptions field includes: initialPageParam (the default page param value used for the first request, if not specified at usage site) - required, maxPages (optional limit to how many fetched pages will be kept in cache entry at a time), getNextPageParam (required callback to calculate next page param given existing cached pages and page params), getPreviousPageParam (optional callback to calculate previous page param for backwards fetching). Both initialPageParam and getNextPageParam are required to ensure the infinite query can properly fetch the next page of data.

PageParamFunction signature

The PageParamFunction type signature is: (currentPage: DataType, allPages: DataType[], currentPageParam: PageParam, allPageParams: PageParam[], queryArg: QueryArg) => PageParam | undefined | null. A page param can be any value: numbers, strings, objects, arrays, etc. 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.

Automatic hook generation for infinite query endpoints

RTK Query automatically generates React hooks for infinite query endpoints based on the endpoint name. An endpoint field like 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.

Three types of infinite query hooks

There are 3 infinite query-related hooks: 1) useInfiniteQuery - composes useInfiniteQuerySubscription and useInfiniteQueryState, is the primary hook, automatically triggers fetches and subscribes the component to cached data, and reads request status and cached data from Redux store. 2) useInfiniteQuerySubscription - returns refetch function and fetchNext/PreviousPage functions, accepts all hooks options, automatically triggers refetches and subscribes to cached data. 3) useInfiniteQueryState - returns query state and accepts skip and selectFromResult, reads request status and cached data from Redux store.

Infinite query hook parameters

The infinite query hooks expect two parameters: (queryArg?, queryOptions?). The queryOptions object accepts all the same parameters as useQuery, including skip, selectFromResult, and refetching/polling options. Unlike normal query hooks, the query or queryFn callbacks receive a page param value to generate the URL or make the request, instead of the query arg. By default, the initialPageParam value specified in the endpoint is used for the first request, then getNext/PreviousPageParam callbacks calculate further page params.

Override initialPageParam at hook usage

To start from a different page param than the endpoint default, you can override the initialPageParam by passing it as part of the hook options: const { data } = useGetPokemonInfiniteQuery('fire', { initialPageParam: 3 }). The next and previous page params will still be calculated as needed.

Infinite query hook return values

Infinite query hooks return the same result object as normal query hooks, but with additional fields specific to infinite queries and a different structure for data and currentData. data/currentData contain the {pages, pageParams} infinite query object with all fetched pages. hasNextPage/hasPreviousPage indicate when another page should be available to fetch in that direction. isFetchingNext/PreviousPage indicate when the isFetching flag represents a fetch in that direction. isFetchNext/PreviousPageError indicate when the isError flag represents an error for a failed fetch in that direction. fetchNext/PreviousPage are methods that trigger a fetch for another page in that direction.

Overlapping page fetches not possible

Since all pages are stored in a single cache entry, there can only be one request in progress at a time. RTK Query has logic built in to bail out of running a new request if there is already a request in flight for that cache entry. If you call fetchNextPage() while an existing request is in progress, the second call won't actually execute a request. Check the isFetching flag or await the previous fetchNextPage() promise first. The promise returned from fetchNextPage() has a promise.abort() method attached that will force the earlier request to reject and not save the results, which marks the cache entry as errored but data still exists.

Infinite query refetching behavior

When an infinite query endpoint is refetched (due to tag invalidation, polling, arg change, or manual refetching), RTK Query's default behavior is sequentially refetching all pages currently in the cache to ensure the client works with latest data and avoids stale cursors or duplicate records. If the cache entry is removed and re-added, it starts with only fetching the initial page. The refetchCachedPages option can override this to only refetch the first page, shrinking the cache from N pages to 1 page. It can be defined on the endpoint as part of infiniteQueryOptions, passed as an option to useInfiniteQuery hook, or passed as an option to endpoint.initiate() or refetch method.

maxPages option for limiting cache entry size

By default, there is no limit to the number of stored pages in the pages array. If you need to limit the number of stored pages for memory usage reasons, 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 cached page params of [1, 2, 3], calling fetchNextPage() would result in page 1 being dropped and cached pages becoming [2, 3, 4].

Schema validation for infinite query endpoints

Endpoints can use Standard Schema compliant libraries for runtime validation of query args, responses, and errors. Most commonly, you'll want to use responseSchema to validate the server response, or rawResponseSchema when using transformResponse. With TypeScript, schemas can be used to infer the type of that value instead of having to declare it manually.

Infinite query definition example with basic pagination

Example of infinite query endpoint with basic pagination: ```ts type Pokemon = { id: string name: string } const pokemonApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }), endpoints: (build) => ({ getInfinitePokemonWithMax: build.infiniteQuery<Pokemon[], string, number>({ infiniteQueryOptions: { initialPageParam: 1, maxPages: 3, getNextPageParam: ( lastPage, allPages, lastPageParam, allPageParams, queryArg, ) => lastPageParam + 1, getPreviousPageParam: ( firstPage, allPages, firstPageParam, allPageParams, queryArg, ) => { return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, query({ queryArg, pageParam }) { return `/type/${queryArg}?page=${pageParam}` }, }), }), }) ```

Infinite query hook usage example with component

Example of infinite query endpoint definition and hook usage: ```tsx type Pokemon = { id: string name: string } const pokemonApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }), endpoints: (build) => ({ getPokemon: build.infiniteQuery<Pokemon[], string, number>({ infiniteQueryOptions: { initialPageParam: 1, getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => lastPageParam + 1, }, query({ queryArg, pageParam }) { return `/type/${queryArg}?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 ( <div> <div>Type: {pokemonType}</div> <div> {allResults.map((pokemon, i: number | null | undefined) => ( <div key={i}>{pokemon.name}</div> ))} </div> <button onClick={() => handleNextPage()}>Fetch More</button> <button onClick={() => handleRefetch()}>Refetch</button> </div> ) } ```

Basic pagination pattern example

Example of basic pagination with simple page numbers: ```ts const pokemonApi = createApi({ baseQuery, endpoints: (build) => ({ getInfinitePokemon: build.infiniteQuery<Pokemon[], string, number>({ infiniteQueryOptions: { initialPageParam: 0, getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1, getPreviousPageParam: (firstPage, allPages, firstPageParam) => { return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, query({ pageParam }) { return `https://example.com/listItems?page=${pageParam}` }, }), }), }) ```

Pagination with page size pattern example

Example of pagination with page number and size that uses totalPages from response: ```ts type ProjectsResponse = { projects: Project[] serverTime: string totalPages: number } type ProjectsInitialPageParam = { page: number size: number } const projectsApi = createApi({ baseQuery, endpoints: (build) => ({ projectsPaginated: build.infiniteQuery< ProjectsResponse, void, ProjectsInitialPageParam >({ infiniteQueryOptions: { initialPageParam: { page: 0, size: 20, }, getNextPageParam: ( lastPage, allPages, lastPageParam, allPageParams, ) => { const nextPage = lastPageParam.page + 1 const remainingPages = lastPage?.totalPages - nextPage if (remainingPages <= 0) { return undefined } return { ...lastPageParam, page: nextPage, } }, getPreviousPageParam: ( firstPage, allPages, firstPageParam, allPageParams, ) => { const prevPage = firstPageParam.page - 1 if (prevPage < 0) return undefined return { ...firstPageParam, page: prevPage, } }, }, query: ({ pageParam: { page, size } }) => { return `https://example.com/api/projectsPaginated?page=${page}&size=${size}` }, }), }), }) ```

Bidirectional cursors pattern example

Example of bidirectional cursor-based pagination using server-provided cursor values: ```ts type ProjectsCursorPaginated = { projects: Project[] serverTime: string pageInfo: { startCursor: number endCursor: number hasNextPage: boolean hasPreviousPage: boolean } } type ProjectsInitialPageParam = { before?: number around?: number after?: number limit: number } type QueryParamLimit = number const projectsApi = createApi({ baseQuery, endpoints: (build) => ({ getProjectsBidirectionalCursor: build.infiniteQuery< ProjectsCursorPaginated, QueryParamLimit, ProjectsInitialPageParam >({ infiniteQueryOptions: { initialPageParam: { limit: 10 }, getPreviousPageParam: ( firstPage, allPages, firstPageParam, allPageParams, ) => { if (!firstPage.pageInfo.hasPreviousPage) { return undefined } return { before: firstPage.pageInfo.startCursor, limit: firstPageParam.limit, } }, getNextPageParam: ( lastPage, allPages, lastPageParam, allPageParams, ) => { if (!lastPage.pageInfo.hasNextPage) { return undefined } return { after: lastPage.pageInfo.endCursor, limit: lastPageParam.limit, } }, }, query: ({ pageParam: { before, after, around, limit } }) => { const params = new URLSearchParams() params.append('limit', String(limit)) if (after != null) { params.append('after', String(after)) } else if (before != null) { params.append('before', String(before)) } else if (around != null) { params.append('around', String(around)) } return `https://example.com/api/projectsBidirectionalCursor?${params.toString()}` }, }), }), }) ```

Limit and offset pagination pattern example

Example of limit and offset pagination: ```ts export type ProjectsResponse = { projects: Project[] numFound: number serverTime: string } type ProjectsInitialPageParam = { offset: number limit: number } const projectsApi = createApi({ baseQuery, endpoints: (build) => ({ projectsLimitOffset: build.infiniteQuery< ProjectsResponse, void, ProjectsInitialPageParam >({ infiniteQueryOptions: { initialPageParam: { offset: 0, limit: 20, }, getNextPageParam: ( lastPage, allPages, lastPageParam, allPageParams, ) => { const nextOffset = lastPageParam.offset + lastPageParam.limit const remainingItems = lastPage?.numFound - nextOffset if (remainingItems <= 0) { return undefined } return { ...lastPageParam, offset: nextOffset, } }, getPreviousPageParam: ( firstPage, allPages, firstPageParam, allPageParams, ) => { const prevOffset = firstPageParam.offset - firstPageParam.limit if (prevOffset < 0) return undefined return { ...firstPageParam, offset: firstPageParam.offset - firstPageParam.limit, } }, }, query: ({ pageParam: { offset, limit } }) => { return `https://example.com/api/projectsLimitOffset?offset=${offset}&limit=${limit}` }, }), }), }) ```

Schema validation example for infinite queries

Example of using responseSchema and rawResponseSchema with infinite queries: ```ts import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import * as v from 'valibot' const pokemonSchema = v.object({ id: v.number(), name: v.string(), }) type Pokemon = v.InferOutput<typeof pokemonSchema> const transformedPokemonSchema = v.object({ ...pokemonSchema.entries, id: v.string(), }) type TransformedPokemon = v.InferOutput<typeof transformedPokemonSchema> const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }), endpoints: (build) => ({ getInfinitePokemon: build.infiniteQuery<Pokemon[], string, number>({ query: ({ queryArg, pageParam }) => `type/${queryArg}?page=${pageParam}`, argSchema: v.object({ queryArg: v.string(), pageParam: v.number(), }), responseSchema: v.array(pokemonSchema), }), getTransformedPokemon: build.infiniteQuery< TransformedPokemon[], string, number >({ query: ({ queryArg, pageParam }) => `type/${queryArg}?page=${pageParam}`, argSchema: v.object({ queryArg: v.string(), pageParam: v.number(), }), rawResponseSchema: v.array(pokemonSchema), transformResponse: (response) => response.map((pokemon) => ({ ...pokemon, id: String(pokemon.id), })), responseSchema: v.array(transformedPokemonSchema), }), }), }) ```

argSchema for infinite queries must include queryArg and pageParam

When using argSchema for infinite query endpoints, the schema must have both queryArg and pageParam fields defined, not just a single arg.

Give your agent this brain