Skip to content

Commit

Permalink
Nest {queryArg, pageParam} for infinite queries (#4826)
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson authored Jan 23, 2025
1 parent dbce780 commit e18b966
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 31 deletions.
18 changes: 14 additions & 4 deletions docs/rtk-query/api/createApi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export type QueryDefinition<
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.
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. The `query` and `queryFn` methods will receive a combined `{queryArg, pageParam}` object as the argument, rather than just the `queryArg` by itself.
```ts title="Infinite Query endpoint definition" no-transpile
export type PageParamFunction<DataType, PageParam> = (
Expand All @@ -238,6 +238,11 @@ export type PageParamFunction<DataType, PageParam> = (
allPageParams: Array<PageParam>,
) => PageParam | undefined | null

type InfiniteQueryCombinedArg<QueryArg, PageParam> = {
queryArg: QueryArg
pageParam: PageParam
}

export type InfiniteQueryDefinition<
QueryArg,
PageParam,
Expand All @@ -247,9 +252,14 @@ export type InfiniteQueryDefinition<
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<PageParam, BaseQuery, TagTypes, InfiniteData<ResultType>> & {
// but store the `{pages, pageParams}` structure, and receive an object
// with both `{queryArg, pageParam}` as the arg for `query` and `queryFn`.
QueryDefinition<
InfiniteQueryCombinedArg<QueryArg, PageParam>,
BaseQuery,
TagTypes,
InfiniteData<ResultType>
> & {
/**
* Required options to configure the infinite query behavior.
* `initialPageParam` and `getNextPageParam` are required, to
Expand Down
20 changes: 11 additions & 9 deletions docs/rtk-query/usage/infinite-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ With standard query endpoints:
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.
- However, there is a separation between the "query arg" used for the cache key, and the "page param" used to fetch a specific page. Since both are useful for determining what to fetch, your `query` and `queryFn` methods will receive a combined object with `{queryArg, pageParam}` as the first argument, instead of just the `queryArg` by itself.
- 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:
Expand Down Expand Up @@ -100,7 +100,7 @@ If there is no possible page to fetch in that direction, the callback should ret
### Infinite Query Definition Example
A complete example of this might look like:
A complete example of this for a fictional Pokemon API service might look like:
```ts no-transpile title="Infinite Query definition example"
type Pokemon = {
Expand All @@ -109,11 +109,12 @@ type Pokemon = {
}

const pokemonApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }),
endpoints: (build) => ({
// 3 TS generics: page contents, query arg, page param
getInfinitePokemonWithMax: build.infiniteQuery<Pokemon[], string, number>({
infiniteQueryOptions: {
initialPageParam: 0,
initialPageParam: 1,
maxPages: 3,
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
lastPageParam + 1,
Expand All @@ -126,8 +127,9 @@ const pokemonApi = createApi({
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
return `https://example.com/listItems?page=${pageParam}`
// The `query` function receives `{queryArg, pageParam}` as its argument
query({ queryArg, pageParam }) {
return `/type/${queryArg}?page=${pageParam}`
},
}),
}),
Expand Down Expand Up @@ -192,16 +194,16 @@ type Pokemon = {
}

const pokemonApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }),
endpoints: (build) => ({
getPokemon: build.infiniteQuery<Pokemon[], string, number>({
infiniteQueryOptions: {
initialPageParam: 0,
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
lastPageParam + 1,
},
query(pageParam) {
return `https://example.com/listItems?page=${pageParam}`
query({ queryArg, pageParam }) {
return `/type/${queryArg}?page=${pageParam}`
},
}),
}),
Expand Down
14 changes: 10 additions & 4 deletions packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
EndpointDefinition,
EndpointDefinitions,
InfiniteQueryArgFrom,
InfiniteQueryCombinedArg,
InfiniteQueryDefinition,
MutationDefinition,
PageParamFrom,
Expand Down Expand Up @@ -464,7 +465,7 @@ export function buildThunks<
transformFieldName: 'transformResponse' | 'transformErrorResponse',
): TransformCallback => {
return endpointDefinition.query && endpointDefinition[transformFieldName]
? endpointDefinition[transformFieldName]!
? (endpointDefinition[transformFieldName]! as TransformCallback)
: defaultTransformResponse
}

Expand Down Expand Up @@ -523,7 +524,12 @@ export function buildThunks<
return Promise.resolve({ data })
}

const pageResponse = await executeRequest(param)
const finalQueryArg: InfiniteQueryCombinedArg<any, any> = {
queryArg: arg.originalArgs,
pageParam: param,
}

const pageResponse = await executeRequest(finalQueryArg)

const addTo = previous ? addToStart : addToEnd

Expand All @@ -548,13 +554,13 @@ export function buildThunks<
result = forceQueryFn()
} else if (endpointDefinition.query) {
result = await baseQuery(
endpointDefinition.query(finalQueryArg),
endpointDefinition.query(finalQueryArg as any),
baseQueryApi,
extraOptions as any,
)
} else {
result = await endpointDefinition.queryFn(
finalQueryArg,
finalQueryArg as any,
baseQueryApi,
extraOptions as any,
(arg) => baseQuery(arg, baseQueryApi, extraOptions as any),
Expand Down
13 changes: 11 additions & 2 deletions packages/toolkit/src/query/endpointDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ export interface InfiniteQueryExtraOptions<
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
query({pageParam}) {
return `https://example.com/listItems?page=${pageParam}`
},
}),
Expand Down Expand Up @@ -737,7 +737,11 @@ export type InfiniteQueryDefinition<
ReducerPath extends string = string,
> =
// Intentionally use `PageParam` as the `QueryArg` type
BaseEndpointDefinition<PageParam, BaseQuery, ResultType> &
BaseEndpointDefinition<
InfiniteQueryCombinedArg<QueryArg, PageParam>,
BaseQuery,
ResultType
> &
InfiniteQueryExtraOptions<
TagTypes,
ResultType,
Expand Down Expand Up @@ -1071,6 +1075,11 @@ export type PageParamFrom<
> =
D extends InfiniteQueryDefinition<any, infer PP, any, any, any> ? PP : unknown

export type InfiniteQueryCombinedArg<QueryArg, PageParam> = {
queryArg: QueryArg
pageParam: PageParam
}

export type TagTypesFromApi<T> =
T extends Api<any, any, any, infer TagTypes> ? TagTypes : never

Expand Down
7 changes: 4 additions & 3 deletions packages/toolkit/src/query/tests/buildHooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1710,7 +1710,7 @@ describe('hooks tests', () => {
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
query({ pageParam }) {
return `https://example.com/listItems?page=${pageParam}`
},
}),
Expand Down Expand Up @@ -1738,7 +1738,7 @@ describe('hooks tests', () => {
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
query({ pageParam }) {
return `https://example.com/listItems?page=${pageParam}`
},
}),
Expand Down Expand Up @@ -2035,7 +2035,8 @@ describe('hooks tests', () => {
}
},
},
query: ({ offset, limit }) => {
query: ({ pageParam }) => {
const { offset, limit } = pageParam
return {
url: `https://example.com/api/projectsLimitOffset?offset=${offset}&limit=${limit}`,
method: 'GET',
Expand Down
3 changes: 2 additions & 1 deletion packages/toolkit/src/query/tests/infiniteQueries.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ describe('Infinite queries', () => {
return lastPageParam + 1
},
},
query(pageParam) {
query({ pageParam, queryArg }) {
expectTypeOf(pageParam).toBeNumber()
expectTypeOf(queryArg).toBeString()

return `https://example.com/listItems?page=${pageParam}`
},
Expand Down
16 changes: 8 additions & 8 deletions packages/toolkit/src/query/tests/infiniteQueries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('Infinite queries', () => {
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
query({ pageParam }) {
return `https://example.com/listItems?page=${pageParam}`
},
}),
Expand All @@ -83,7 +83,7 @@ describe('Infinite queries', () => {
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
query({ pageParam }) {
return `https://example.com/listItems?page=${pageParam}`
},
},
Expand All @@ -110,10 +110,10 @@ describe('Infinite queries', () => {
tagTypes: ['Counter'],
endpoints: (build) => ({
counters: build.infiniteQuery<HitCounter, string, number>({
queryFn(page) {
queryFn({ pageParam }) {
hitCounter++

return { data: { page, hitCounter } }
return { data: { page: pageParam, hitCounter } }
},
infiniteQueryOptions: {
initialPageParam: 0,
Expand Down Expand Up @@ -663,7 +663,7 @@ describe('Infinite queries', () => {
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
query({ pageParam }) {
return `https://example.com/listItems?page=${pageParam}`
},
}),
Expand Down Expand Up @@ -809,7 +809,7 @@ describe('Infinite queries', () => {
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
query({ pageParam }) {
return `https://example.com/listItems?page=${pageParam}`
},
async onCacheEntryAdded(arg, api) {
Expand Down Expand Up @@ -879,14 +879,14 @@ describe('Infinite queries', () => {
allPageParams,
) => lastPageParam + 1,
},
query(pageParam) {
query({ pageParam }) {
return `https://example.com/listItems?page=${pageParam}`
},
transformResponse(baseQueryReturnValue: Pokemon[], meta, arg) {
expect(Array.isArray(baseQueryReturnValue)).toBe(true)
return {
items: baseQueryReturnValue,
page: arg,
page: arg.pageParam,
}
},
}),
Expand Down

0 comments on commit e18b966

Please sign in to comment.