diff --git a/.changeset/seven-monkeys-fetch.md b/.changeset/seven-monkeys-fetch.md new file mode 100644 index 000000000..10e4e6e32 --- /dev/null +++ b/.changeset/seven-monkeys-fetch.md @@ -0,0 +1,5 @@ +--- +"openapi-react-query": minor +--- + +[#2169](https://github.com/openapi-ts/openapi-typescript/pull/2169): Infer returned `data` type from `select` option when used with the `useInfiniteQuery` method. diff --git a/docs/.vitepress/en.ts b/docs/.vitepress/en.ts index a15fe27cb..4fa344bdd 100644 --- a/docs/.vitepress/en.ts +++ b/docs/.vitepress/en.ts @@ -80,6 +80,10 @@ export default defineConfig({ text: "useSuspenseQuery", link: "/use-suspense-query", }, + { + text: "useInfiniteQuery", + link: "/use-infinite-query", + }, { text: "queryOptions", link: "/query-options", diff --git a/docs/openapi-react-query/use-infinite-query.md b/docs/openapi-react-query/use-infinite-query.md index 86bcbad1e..7824baa1a 100644 --- a/docs/openapi-react-query/use-infinite-query.md +++ b/docs/openapi-react-query/use-infinite-query.md @@ -105,6 +105,7 @@ const query = $api.useInfiniteQuery( - Only required if the OpenApi schema requires parameters. - The options `params` are used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) for more information. - `infiniteQueryOptions` + - `pageParamName` The query param name used for pagination, `"cursor"` by default. - The original `useInfiniteQuery` options. - [See more information](https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery) - `queryClient` diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts index 855b249c9..97fd9ce1e 100644 --- a/packages/openapi-react-query/src/index.ts +++ b/packages/openapi-react-query/src/index.ts @@ -110,7 +110,7 @@ export type UseInfiniteQueryMethod, + InferSelectReturnType, Options["select"]>, Response["data"], QueryKey, unknown @@ -125,7 +125,10 @@ export type UseInfiniteQueryMethod, options: Options, queryClient?: QueryClient, -) => UseInfiniteQueryResult, Response["error"]>; +) => UseInfiniteQueryResult< + InferSelectReturnType, Options["select"]>, + Response["error"] +>; export type UseSuspenseQueryMethod>, Media extends MediaType> = < Method extends HttpMethod, diff --git a/packages/openapi-react-query/test/index.test.tsx b/packages/openapi-react-query/test/index.test.tsx index 5ccda7a42..1062bafd7 100644 --- a/packages/openapi-react-query/test/index.test.tsx +++ b/packages/openapi-react-query/test/index.test.tsx @@ -1087,5 +1087,69 @@ describe("client", () => { const allItems = result.current.data?.pages.flatMap((page) => page.items); expect(allItems).toEqual([1, 2, 3, 4, 5, 6]); }); + it("should use return type from select option", async () => { + const fetchClient = createFetchClient({ baseUrl }); + const client = createClient(fetchClient); + + // First page request handler + const firstRequestHandler = useMockRequestHandler({ + baseUrl, + method: "get", + path: "/paginated-data", + status: 200, + body: { items: [1, 2, 3], nextPage: 1 }, + }); + + const { result, rerender } = renderHook( + () => + client.useInfiniteQuery( + "get", + "/paginated-data", + { + params: { + query: { + limit: 3, + }, + }, + }, + { + getNextPageParam: (lastPage) => lastPage.nextPage, + initialPageParam: 0, + select: (data) => data.pages.flatMap((page) => page.items).filter((item) => item !== undefined), + }, + ), + { wrapper }, + ); + + // Wait for initial query to complete + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expectTypeOf(result.current.data).toEqualTypeOf(); + expect(result.current.data).toEqual([1, 2, 3]); + + // Set up mock for second page before triggering next page fetch + const secondRequestHandler = useMockRequestHandler({ + baseUrl, + method: "get", + path: "/paginated-data", + status: 200, + body: { items: [4, 5, 6], nextPage: 2 }, + }); + + // Fetch next page + await act(async () => { + await result.current.fetchNextPage(); + // Force a rerender to ensure state is updated + rerender(); + }); + + // Wait for second page to be fetched and verify loading states + await waitFor(() => { + expect(result.current.isFetching).toBe(false); + expect(result.current.hasNextPage).toBe(true); + }); + + expect(result.current.data).toEqual([1, 2, 3, 4, 5, 6]); + }); }); });