diff --git a/docs/openapi-react-query/use-infinite-query.md b/docs/openapi-react-query/use-infinite-query.md index 86bcbad1e..1b19dac67 100644 --- a/docs/openapi-react-query/use-infinite-query.md +++ b/docs/openapi-react-query/use-infinite-query.md @@ -35,6 +35,7 @@ const PostList = () => { }, { getNextPageParam: (lastPage) => lastPage.nextPage, + pageParamName: "cursor", initialPageParam: 0, } ); diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts index 855b249c9..1f58497c2 100644 --- a/packages/openapi-react-query/src/index.ts +++ b/packages/openapi-react-query/src/index.ts @@ -22,8 +22,9 @@ import type { MaybeOptionalInit, Client as FetchClient, DefaultParamsOption, + ParamsOption, } from "openapi-fetch"; -import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers"; +import type { FilterKeys, HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers"; // Helper type to dynamically infer the type from the `select` property type InferSelectReturnType = TSelect extends (data: TData) => infer R ? R : TData; @@ -106,6 +107,8 @@ export type UseInfiniteQueryMethod, Init extends MaybeOptionalInit, Response extends Required>, + Query extends ParamsOption>["params"] extends { query: infer Query } ? Query : never, + PageParamName extends keyof Query, Options extends Omit< UseInfiniteQueryOptions< Response["data"], @@ -113,17 +116,17 @@ export type UseInfiniteQueryMethod, Response["data"], QueryKey, - unknown + NonNullable >, "queryKey" | "queryFn" - > & { - pageParamName?: string; - }, + >, >( method: Method, url: Path, init: InitWithUnknowns, - options: Options, + options: Options & { + pageParamName: PageParamName; + }, queryClient?: QueryClient, ) => UseInfiniteQueryResult, Response["error"]>; @@ -216,12 +219,12 @@ export default function createClient useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns, options), queryClient), useInfiniteQuery: (method, path, init, options, queryClient) => { - const { pageParamName = "cursor", ...restOptions } = options; + const { pageParamName, ...restOptions } = options; const { queryKey } = queryOptions(method, path, init); return useInfiniteQuery( { queryKey, - queryFn: async ({ queryKey: [method, path, init], pageParam = 0, signal }) => { + queryFn: async ({ queryKey: [method, path, init], pageParam, signal }) => { const mth = method.toUpperCase() as Uppercase; const fn = client[mth] as ClientMethod; const mergedInit = { diff --git a/packages/openapi-react-query/test/index.test.tsx b/packages/openapi-react-query/test/index.test.tsx index 65420164a..171b904c0 100644 --- a/packages/openapi-react-query/test/index.test.tsx +++ b/packages/openapi-react-query/test/index.test.tsx @@ -862,6 +862,7 @@ describe("client", () => { { getNextPageParam: (lastPage) => lastPage.nextPage, initialPageParam: 0, + pageParamName: "cursor", }, ), { wrapper }, @@ -947,6 +948,7 @@ describe("client", () => { { getNextPageParam: (lastPage) => lastPage.nextPage, initialPageParam: 0, + pageParamName: "cursor", select: (data) => ({ pages: [...data.pages].reverse(), pageParams: [...data.pageParams].reverse(), @@ -1001,91 +1003,5 @@ describe("client", () => { const allItems = result.current.data?.pages.flatMap((page) => page.items); expect(allItems).toEqual([4, 5, 6, 1, 2, 3]); }); - it("should use custom cursor params", 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, - pageParamName: "follow_cursor", - }, - ), - { wrapper }, - ); - - // Wait for initial query to complete - await waitFor(() => expect(result.current.isSuccess).toBe(true)); - - // Verify first request - const firstRequestUrl = firstRequestHandler.getRequestUrl(); - expect(firstRequestUrl?.searchParams.get("limit")).toBe("3"); - expect(firstRequestUrl?.searchParams.get("follow_cursor")).toBe("0"); - - // 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?.pages).toHaveLength(2); - }); - - // Verify second request - const secondRequestUrl = secondRequestHandler.getRequestUrl(); - expect(secondRequestUrl?.searchParams.get("limit")).toBe("3"); - expect(secondRequestUrl?.searchParams.get("follow_cursor")).toBe("1"); - - expect(result.current.data).toBeDefined(); - expect(result.current.data?.pages[0].nextPage).toBe(1); - - expect(result.current.data).toBeDefined(); - expect(result.current.data?.pages[1].nextPage).toBe(2); - - // Verify the complete data structure - expect(result.current.data?.pages).toEqual([ - { items: [1, 2, 3], nextPage: 1 }, - { items: [4, 5, 6], nextPage: 2 }, - ]); - - // Verify we can access all items through pages - const allItems = result.current.data?.pages.flatMap((page) => page.items); - expect(allItems).toEqual([1, 2, 3, 4, 5, 6]); - }); }); });