Skip to content

Commit 3ef38b8

Browse files
authored
feat(openapi-react-query): support for useInfiniteQuery() (#2117)
* Working function * So far so good * Reusable options * More tests * Fixe select types * Better usage * Lint * Added changeset * Add pageParamName * Remove custom annotation
1 parent 248195d commit 3ef38b8

File tree

5 files changed

+292
-94
lines changed

5 files changed

+292
-94
lines changed

.changeset/mighty-comics-worry.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-react-query": minor
3+
---
4+
5+
Implements useInfiniteQuery() in openapi-react-query

packages/openapi-react-query/src/index.ts

+67-74
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import {
33
type UseMutationResult,
44
type UseQueryOptions,
55
type UseQueryResult,
6-
type UseSuspenseQueryOptions,
7-
type UseSuspenseQueryResult,
6+
type InfiniteData,
87
type UseInfiniteQueryOptions,
98
type UseInfiniteQueryResult,
9+
type UseSuspenseQueryOptions,
10+
type UseSuspenseQueryResult,
1011
type QueryClient,
1112
type QueryFunctionContext,
1213
type SkipToken,
@@ -15,7 +16,13 @@ import {
1516
useSuspenseQuery,
1617
useInfiniteQuery,
1718
} from "@tanstack/react-query";
18-
import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient } from "openapi-fetch";
19+
import type {
20+
ClientMethod,
21+
FetchResponse,
22+
MaybeOptionalInit,
23+
Client as FetchClient,
24+
DefaultParamsOption,
25+
} from "openapi-fetch";
1926
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers";
2027

2128
// Helper type to dynamically infer the type from the `select` property
@@ -93,6 +100,32 @@ export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>,
93100
: [InitWithUnknowns<Init>, Options?, QueryClient?]
94101
) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
95102

103+
export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
104+
Method extends HttpMethod,
105+
Path extends PathsWithMethod<Paths, Method>,
106+
Init extends MaybeOptionalInit<Paths[Path], Method>,
107+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
108+
Options extends Omit<
109+
UseInfiniteQueryOptions<
110+
Response["data"],
111+
Response["error"],
112+
InfiniteData<Response["data"]>,
113+
Response["data"],
114+
QueryKey<Paths, Method, Path>,
115+
unknown
116+
>,
117+
"queryKey" | "queryFn"
118+
> & {
119+
pageParamName?: string;
120+
},
121+
>(
122+
method: Method,
123+
url: Path,
124+
init: InitWithUnknowns<Init>,
125+
options: Options,
126+
queryClient?: QueryClient,
127+
) => UseInfiniteQueryResult<InfiniteData<Response["data"]>, Response["error"]>;
128+
96129
export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
97130
Method extends HttpMethod,
98131
Path extends PathsWithMethod<Paths, Method>,
@@ -115,45 +148,6 @@ export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMetho
115148
: [InitWithUnknowns<Init>, Options?, QueryClient?]
116149
) => UseSuspenseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
117150

118-
export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
119-
Method extends HttpMethod,
120-
Path extends PathsWithMethod<Paths, Method>,
121-
Init extends MaybeOptionalInit<Paths[Path], Method>,
122-
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
123-
>(
124-
method: Method,
125-
url: Path,
126-
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
127-
? [
128-
InitWithUnknowns<Init>?,
129-
Omit<
130-
UseInfiniteQueryOptions<
131-
Response["data"],
132-
Response["error"],
133-
Response["data"],
134-
number,
135-
QueryKey<Paths, Method, Path>
136-
>,
137-
"queryKey" | "queryFn"
138-
>?,
139-
QueryClient?,
140-
]
141-
: [
142-
InitWithUnknowns<Init>,
143-
Omit<
144-
UseInfiniteQueryOptions<
145-
Response["data"],
146-
Response["error"],
147-
Response["data"],
148-
number,
149-
QueryKey<Paths, Method, Path>
150-
>,
151-
"queryKey" | "queryFn"
152-
>?,
153-
QueryClient?,
154-
]
155-
) => UseInfiniteQueryResult<Response["data"], Response["error"]>;
156-
157151
export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
158152
Method extends HttpMethod,
159153
Path extends PathsWithMethod<Paths, Method>,
@@ -210,46 +204,45 @@ export default function createClient<Paths extends {}, Media extends MediaType =
210204
...options,
211205
});
212206

213-
const infiniteQueryOptions = <
214-
Method extends HttpMethod,
215-
Path extends PathsWithMethod<Paths, Method>,
216-
Init extends MaybeOptionalInit<Paths[Path], Method & keyof Paths[Path]>,
217-
Response extends Required<FetchResponse<any, Init, Media>>,
218-
>(
219-
method: Method,
220-
path: Path,
221-
init?: InitWithUnknowns<Init>,
222-
options?: Omit<
223-
UseInfiniteQueryOptions<
224-
Response["data"],
225-
Response["error"],
226-
Response["data"],
227-
number,
228-
QueryKey<Paths, Method, Path>
229-
>,
230-
"queryKey" | "queryFn"
231-
>,
232-
) => ({
233-
queryKey: [method, path, init] as const,
234-
queryFn,
235-
...options,
236-
});
237-
238207
return {
239208
queryOptions,
240209
useQuery: (method, path, ...[init, options, queryClient]) =>
241210
useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
242211
useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>
243212
useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
244-
useInfiniteQuery: (method, path, ...[init, options, queryClient]) => {
245-
const baseOptions = infiniteQueryOptions(method, path, init as InitWithUnknowns<typeof init>, options as any); // TODO: find a way to avoid as any
213+
useInfiniteQuery: (method, path, init, options, queryClient) => {
214+
const { pageParamName = "cursor", ...restOptions } = options;
215+
246216
return useInfiniteQuery(
247217
{
248-
...baseOptions,
249-
initialPageParam: 0,
250-
getNextPageParam: (lastPage: any, allPages: any[], lastPageParam: number, allPageParams: number[]) =>
251-
options?.getNextPageParam?.(lastPage, allPages, lastPageParam, allPageParams) ?? allPages.length,
252-
} as any,
218+
queryKey: [method, path, init] as const,
219+
queryFn: async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
220+
queryKey: [method, path, init],
221+
pageParam = 0,
222+
signal,
223+
}: QueryFunctionContext<QueryKey<Paths, Method, Path>, unknown>) => {
224+
const mth = method.toUpperCase() as Uppercase<typeof method>;
225+
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
226+
const mergedInit = {
227+
...init,
228+
signal,
229+
params: {
230+
...(init?.params || {}),
231+
query: {
232+
...(init?.params as { query?: DefaultParamsOption })?.query,
233+
[pageParamName]: pageParam,
234+
},
235+
},
236+
};
237+
238+
const { data, error } = await fn(path, mergedInit as any);
239+
if (error) {
240+
throw error;
241+
}
242+
return data;
243+
},
244+
...restOptions,
245+
},
253246
queryClient,
254247
);
255248
},

packages/openapi-react-query/test/fixtures/api.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface paths {
1515
parameters: {
1616
query: {
1717
limit: number;
18+
cursor?: number;
1819
};
1920
header?: never;
2021
path?: never;

packages/openapi-react-query/test/fixtures/api.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ paths:
1111
required: true
1212
schema:
1313
type: integer
14+
- in: query
15+
name: cursor
16+
required: false
17+
schema:
18+
type: integer
1419
responses:
1520
'200':
1621
description: Successful response

0 commit comments

Comments
 (0)