diff --git a/docs/rtk-query/api/createApi.mdx b/docs/rtk-query/api/createApi.mdx index 619a1bc783..b88bfc933f 100644 --- a/docs/rtk-query/api/createApi.mdx +++ b/docs/rtk-query/api/createApi.mdx @@ -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 = ( @@ -238,6 +238,11 @@ export type PageParamFunction = ( allPageParams: Array, ) => PageParam | undefined | null +type InfiniteQueryCombinedArg = { + queryArg: QueryArg + pageParam: PageParam +} + export type InfiniteQueryDefinition< QueryArg, PageParam, @@ -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> & { + // but store the `{pages, pageParams}` structure, and receive an object + // with both `{queryArg, pageParam}` as the arg for `query` and `queryFn`. + QueryDefinition< + InfiniteQueryCombinedArg, + BaseQuery, + TagTypes, + InfiniteData + > & { /** * Required options to configure the infinite query behavior. * `initialPageParam` and `getNextPageParam` are required, to diff --git a/docs/rtk-query/usage/infinite-queries.mdx b/docs/rtk-query/usage/infinite-queries.mdx index 3b7335c591..c009498c3f 100644 --- a/docs/rtk-query/usage/infinite-queries.mdx +++ b/docs/rtk-query/usage/infinite-queries.mdx @@ -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: @@ -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 = { @@ -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({ infiniteQueryOptions: { - initialPageParam: 0, + initialPageParam: 1, maxPages: 3, getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => lastPageParam + 1, @@ -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}` }, }), }), @@ -192,7 +194,7 @@ 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({ infiniteQueryOptions: { @@ -200,8 +202,8 @@ const pokemonApi = createApi({ getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => lastPageParam + 1, }, - query(pageParam) { - return `https://example.com/listItems?page=${pageParam}` + query({ queryArg, pageParam }) { + return `/type/${queryArg}?page=${pageParam}` }, }), }), diff --git a/packages/toolkit/src/query/core/buildThunks.ts b/packages/toolkit/src/query/core/buildThunks.ts index 32fb1f5e04..22588df9b9 100644 --- a/packages/toolkit/src/query/core/buildThunks.ts +++ b/packages/toolkit/src/query/core/buildThunks.ts @@ -20,6 +20,7 @@ import type { EndpointDefinition, EndpointDefinitions, InfiniteQueryArgFrom, + InfiniteQueryCombinedArg, InfiniteQueryDefinition, MutationDefinition, PageParamFrom, @@ -464,7 +465,7 @@ export function buildThunks< transformFieldName: 'transformResponse' | 'transformErrorResponse', ): TransformCallback => { return endpointDefinition.query && endpointDefinition[transformFieldName] - ? endpointDefinition[transformFieldName]! + ? (endpointDefinition[transformFieldName]! as TransformCallback) : defaultTransformResponse } @@ -523,7 +524,12 @@ export function buildThunks< return Promise.resolve({ data }) } - const pageResponse = await executeRequest(param) + const finalQueryArg: InfiniteQueryCombinedArg = { + queryArg: arg.originalArgs, + pageParam: param, + } + + const pageResponse = await executeRequest(finalQueryArg) const addTo = previous ? addToStart : addToEnd @@ -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), diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index 121009a709..888700819d 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -643,7 +643,7 @@ export interface InfiniteQueryExtraOptions< return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, - query(pageParam) { + query({pageParam}) { return `https://example.com/listItems?page=${pageParam}` }, }), @@ -737,7 +737,11 @@ export type InfiniteQueryDefinition< ReducerPath extends string = string, > = // Intentionally use `PageParam` as the `QueryArg` type - BaseEndpointDefinition & + BaseEndpointDefinition< + InfiniteQueryCombinedArg, + BaseQuery, + ResultType + > & InfiniteQueryExtraOptions< TagTypes, ResultType, @@ -1071,6 +1075,11 @@ export type PageParamFrom< > = D extends InfiniteQueryDefinition ? PP : unknown +export type InfiniteQueryCombinedArg = { + queryArg: QueryArg + pageParam: PageParam +} + export type TagTypesFromApi = T extends Api ? TagTypes : never diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index d6ad241df1..43306638ad 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -1710,7 +1710,7 @@ describe('hooks tests', () => { return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, - query(pageParam) { + query({ pageParam }) { return `https://example.com/listItems?page=${pageParam}` }, }), @@ -1738,7 +1738,7 @@ describe('hooks tests', () => { return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, - query(pageParam) { + query({ pageParam }) { return `https://example.com/listItems?page=${pageParam}` }, }), @@ -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', diff --git a/packages/toolkit/src/query/tests/infiniteQueries.test-d.ts b/packages/toolkit/src/query/tests/infiniteQueries.test-d.ts index 8c4ba2c7b0..e305b5e788 100644 --- a/packages/toolkit/src/query/tests/infiniteQueries.test-d.ts +++ b/packages/toolkit/src/query/tests/infiniteQueries.test-d.ts @@ -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}` }, diff --git a/packages/toolkit/src/query/tests/infiniteQueries.test.ts b/packages/toolkit/src/query/tests/infiniteQueries.test.ts index 2f6ec32301..0ef99d513e 100644 --- a/packages/toolkit/src/query/tests/infiniteQueries.test.ts +++ b/packages/toolkit/src/query/tests/infiniteQueries.test.ts @@ -59,7 +59,7 @@ describe('Infinite queries', () => { return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, - query(pageParam) { + query({ pageParam }) { return `https://example.com/listItems?page=${pageParam}` }, }), @@ -83,7 +83,7 @@ describe('Infinite queries', () => { return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, - query(pageParam) { + query({ pageParam }) { return `https://example.com/listItems?page=${pageParam}` }, }, @@ -110,10 +110,10 @@ describe('Infinite queries', () => { tagTypes: ['Counter'], endpoints: (build) => ({ counters: build.infiniteQuery({ - queryFn(page) { + queryFn({ pageParam }) { hitCounter++ - return { data: { page, hitCounter } } + return { data: { page: pageParam, hitCounter } } }, infiniteQueryOptions: { initialPageParam: 0, @@ -663,7 +663,7 @@ describe('Infinite queries', () => { return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, - query(pageParam) { + query({ pageParam }) { return `https://example.com/listItems?page=${pageParam}` }, }), @@ -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) { @@ -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, } }, }),