Skip to content

Commit 91f2e1b

Browse files
committed
Nest {queryArg, pageParam} for infinite queries (#4826)
1 parent 840a1e7 commit 91f2e1b

File tree

7 files changed

+60
-31
lines changed

7 files changed

+60
-31
lines changed

docs/rtk-query/api/createApi.mdx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ export type QueryDefinition<
228228
229229
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.
230230
231-
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.
231+
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.
232232
233233
```ts title="Infinite Query endpoint definition" no-transpile
234234
export type PageParamFunction<DataType, PageParam> = (
@@ -238,6 +238,11 @@ export type PageParamFunction<DataType, PageParam> = (
238238
allPageParams: Array<PageParam>,
239239
) => PageParam | undefined | null
240240

241+
type InfiniteQueryCombinedArg<QueryArg, PageParam> = {
242+
queryArg: QueryArg
243+
pageParam: PageParam
244+
}
245+
241246
export type InfiniteQueryDefinition<
242247
QueryArg,
243248
PageParam,
@@ -247,9 +252,14 @@ export type InfiniteQueryDefinition<
247252
ReducerPath extends string = string,
248253
> =
249254
// Infinite queries have all the same options as query endpoints,
250-
// but store the `{data, pages}` structure, and use the
251-
// `PageParam` type as the `QueryArg` for fetching.
252-
QueryDefinition<PageParam, BaseQuery, TagTypes, InfiniteData<ResultType>> & {
255+
// but store the `{pages, pageParams}` structure, and receive an object
256+
// with both `{queryArg, pageParam}` as the arg for `query` and `queryFn`.
257+
QueryDefinition<
258+
InfiniteQueryCombinedArg<QueryArg, PageParam>,
259+
BaseQuery,
260+
TagTypes,
261+
InfiniteData<ResultType>
262+
> & {
253263
/**
254264
* Required options to configure the infinite query behavior.
255265
* `initialPageParam` and `getNextPageParam` are required, to

docs/rtk-query/usage/infinite-queries.mdx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ With standard query endpoints:
3131
Infinite queries work similarly, but have a couple additional layers:
3232

3333
- You still specify a "query arg", which is still used to generate the unique cache key for this specific cache entry
34-
- 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.
34+
- 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.
3535
- 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.
3636

3737
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:
@@ -100,7 +100,7 @@ If there is no possible page to fetch in that direction, the callback should ret
100100
101101
### Infinite Query Definition Example
102102
103-
A complete example of this might look like:
103+
A complete example of this for a fictional Pokemon API service might look like:
104104
105105
```ts no-transpile title="Infinite Query definition example"
106106
type Pokemon = {
@@ -109,11 +109,12 @@ type Pokemon = {
109109
}
110110

111111
const pokemonApi = createApi({
112-
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
112+
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }),
113113
endpoints: (build) => ({
114+
// 3 TS generics: page contents, query arg, page param
114115
getInfinitePokemonWithMax: build.infiniteQuery<Pokemon[], string, number>({
115116
infiniteQueryOptions: {
116-
initialPageParam: 0,
117+
initialPageParam: 1,
117118
maxPages: 3,
118119
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
119120
lastPageParam + 1,
@@ -126,8 +127,9 @@ const pokemonApi = createApi({
126127
return firstPageParam > 0 ? firstPageParam - 1 : undefined
127128
},
128129
},
129-
query(pageParam) {
130-
return `https://example.com/listItems?page=${pageParam}`
130+
// The `query` function receives `{queryArg, pageParam}` as its argument
131+
query({ queryArg, pageParam }) {
132+
return `/type/${queryArg}?page=${pageParam}`
131133
},
132134
}),
133135
}),
@@ -192,16 +194,16 @@ type Pokemon = {
192194
}
193195

194196
const pokemonApi = createApi({
195-
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
197+
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }),
196198
endpoints: (build) => ({
197199
getPokemon: build.infiniteQuery<Pokemon[], string, number>({
198200
infiniteQueryOptions: {
199201
initialPageParam: 0,
200202
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
201203
lastPageParam + 1,
202204
},
203-
query(pageParam) {
204-
return `https://example.com/listItems?page=${pageParam}`
205+
query({ queryArg, pageParam }) {
206+
return `/type/${queryArg}?page=${pageParam}`
205207
},
206208
}),
207209
}),

packages/toolkit/src/query/core/buildThunks.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
EndpointDefinition,
2121
EndpointDefinitions,
2222
InfiniteQueryArgFrom,
23+
InfiniteQueryCombinedArg,
2324
InfiniteQueryDefinition,
2425
MutationDefinition,
2526
PageParamFrom,
@@ -464,7 +465,7 @@ export function buildThunks<
464465
transformFieldName: 'transformResponse' | 'transformErrorResponse',
465466
): TransformCallback => {
466467
return endpointDefinition.query && endpointDefinition[transformFieldName]
467-
? endpointDefinition[transformFieldName]!
468+
? (endpointDefinition[transformFieldName]! as TransformCallback)
468469
: defaultTransformResponse
469470
}
470471

@@ -523,7 +524,12 @@ export function buildThunks<
523524
return Promise.resolve({ data })
524525
}
525526

526-
const pageResponse = await executeRequest(param)
527+
const finalQueryArg: InfiniteQueryCombinedArg<any, any> = {
528+
queryArg: arg.originalArgs,
529+
pageParam: param,
530+
}
531+
532+
const pageResponse = await executeRequest(finalQueryArg)
527533

528534
const addTo = previous ? addToStart : addToEnd
529535

@@ -548,13 +554,13 @@ export function buildThunks<
548554
result = forceQueryFn()
549555
} else if (endpointDefinition.query) {
550556
result = await baseQuery(
551-
endpointDefinition.query(finalQueryArg),
557+
endpointDefinition.query(finalQueryArg as any),
552558
baseQueryApi,
553559
extraOptions as any,
554560
)
555561
} else {
556562
result = await endpointDefinition.queryFn(
557-
finalQueryArg,
563+
finalQueryArg as any,
558564
baseQueryApi,
559565
extraOptions as any,
560566
(arg) => baseQuery(arg, baseQueryApi, extraOptions as any),

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ export interface InfiniteQueryExtraOptions<
643643
return firstPageParam > 0 ? firstPageParam - 1 : undefined
644644
},
645645
},
646-
query(pageParam) {
646+
query({pageParam}) {
647647
return `https://example.com/listItems?page=${pageParam}`
648648
},
649649
}),
@@ -737,7 +737,11 @@ export type InfiniteQueryDefinition<
737737
ReducerPath extends string = string,
738738
> =
739739
// Intentionally use `PageParam` as the `QueryArg` type
740-
BaseEndpointDefinition<PageParam, BaseQuery, ResultType> &
740+
BaseEndpointDefinition<
741+
InfiniteQueryCombinedArg<QueryArg, PageParam>,
742+
BaseQuery,
743+
ResultType
744+
> &
741745
InfiniteQueryExtraOptions<
742746
TagTypes,
743747
ResultType,
@@ -1071,6 +1075,11 @@ export type PageParamFrom<
10711075
> =
10721076
D extends InfiniteQueryDefinition<any, infer PP, any, any, any> ? PP : unknown
10731077

1078+
export type InfiniteQueryCombinedArg<QueryArg, PageParam> = {
1079+
queryArg: QueryArg
1080+
pageParam: PageParam
1081+
}
1082+
10741083
export type TagTypesFromApi<T> =
10751084
T extends Api<any, any, any, infer TagTypes> ? TagTypes : never
10761085

packages/toolkit/src/query/tests/buildHooks.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1710,7 +1710,7 @@ describe('hooks tests', () => {
17101710
return firstPageParam > 0 ? firstPageParam - 1 : undefined
17111711
},
17121712
},
1713-
query(pageParam) {
1713+
query({ pageParam }) {
17141714
return `https://example.com/listItems?page=${pageParam}`
17151715
},
17161716
}),
@@ -1738,7 +1738,7 @@ describe('hooks tests', () => {
17381738
return firstPageParam > 0 ? firstPageParam - 1 : undefined
17391739
},
17401740
},
1741-
query(pageParam) {
1741+
query({ pageParam }) {
17421742
return `https://example.com/listItems?page=${pageParam}`
17431743
},
17441744
}),
@@ -2035,7 +2035,8 @@ describe('hooks tests', () => {
20352035
}
20362036
},
20372037
},
2038-
query: ({ offset, limit }) => {
2038+
query: ({ pageParam }) => {
2039+
const { offset, limit } = pageParam
20392040
return {
20402041
url: `https://example.com/api/projectsLimitOffset?offset=${offset}&limit=${limit}`,
20412042
method: 'GET',

packages/toolkit/src/query/tests/infiniteQueries.test-d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ describe('Infinite queries', () => {
3636
return lastPageParam + 1
3737
},
3838
},
39-
query(pageParam) {
39+
query({ pageParam, queryArg }) {
4040
expectTypeOf(pageParam).toBeNumber()
41+
expectTypeOf(queryArg).toBeString()
4142

4243
return `https://example.com/listItems?page=${pageParam}`
4344
},

packages/toolkit/src/query/tests/infiniteQueries.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('Infinite queries', () => {
5959
return firstPageParam > 0 ? firstPageParam - 1 : undefined
6060
},
6161
},
62-
query(pageParam) {
62+
query({ pageParam }) {
6363
return `https://example.com/listItems?page=${pageParam}`
6464
},
6565
}),
@@ -83,7 +83,7 @@ describe('Infinite queries', () => {
8383
return firstPageParam > 0 ? firstPageParam - 1 : undefined
8484
},
8585
},
86-
query(pageParam) {
86+
query({ pageParam }) {
8787
return `https://example.com/listItems?page=${pageParam}`
8888
},
8989
},
@@ -110,10 +110,10 @@ describe('Infinite queries', () => {
110110
tagTypes: ['Counter'],
111111
endpoints: (build) => ({
112112
counters: build.infiniteQuery<HitCounter, string, number>({
113-
queryFn(page) {
113+
queryFn({ pageParam }) {
114114
hitCounter++
115115

116-
return { data: { page, hitCounter } }
116+
return { data: { page: pageParam, hitCounter } }
117117
},
118118
infiniteQueryOptions: {
119119
initialPageParam: 0,
@@ -663,7 +663,7 @@ describe('Infinite queries', () => {
663663
return firstPageParam > 0 ? firstPageParam - 1 : undefined
664664
},
665665
},
666-
query(pageParam) {
666+
query({ pageParam }) {
667667
return `https://example.com/listItems?page=${pageParam}`
668668
},
669669
}),
@@ -809,7 +809,7 @@ describe('Infinite queries', () => {
809809
return firstPageParam > 0 ? firstPageParam - 1 : undefined
810810
},
811811
},
812-
query(pageParam) {
812+
query({ pageParam }) {
813813
return `https://example.com/listItems?page=${pageParam}`
814814
},
815815
async onCacheEntryAdded(arg, api) {
@@ -879,14 +879,14 @@ describe('Infinite queries', () => {
879879
allPageParams,
880880
) => lastPageParam + 1,
881881
},
882-
query(pageParam) {
882+
query({ pageParam }) {
883883
return `https://example.com/listItems?page=${pageParam}`
884884
},
885885
transformResponse(baseQueryReturnValue: Pokemon[], meta, arg) {
886886
expect(Array.isArray(baseQueryReturnValue)).toBe(true)
887887
return {
888888
items: baseQueryReturnValue,
889-
page: arg,
889+
page: arg.pageParam,
890890
}
891891
},
892892
}),

0 commit comments

Comments
 (0)