Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nest {queryArg, pageParam} for infinite queries #4826

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading