Skip to content

Commit ccbc2e3

Browse files
authored
feat(openapi-react-query): support for useInfiniteQuery() (#1881)
* feat(react-query): add useInfiniteQuery * feat(react-query): add unit test for useInfiniteQuery * fix(react-query): revert unnecessary import order change * docs(react-query): add useInfiniteQuery docs * fix(react-query): fix lint ci error
1 parent 33b4213 commit ccbc2e3

File tree

5 files changed

+339
-1
lines changed

5 files changed

+339
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
title: useInfiniteQuery
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
The `useInfiniteQuery` method allows you to use the original [useInfiniteQuery](https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries)
8+
9+
- The result is the same as the original function.
10+
- The `queryKey` is `[method, path, params]`.
11+
- `data` and `error` are fully typed.
12+
- You can pass infinite query options as fourth parameter.
13+
14+
::: tip
15+
You can find more information about `useInfiniteQuery` on the [@tanstack/react-query documentation](https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries).
16+
:::
17+
18+
## Example
19+
20+
::: code-group
21+
22+
```tsx [src/app.tsx]
23+
import { $api } from "./api";
24+
const PostList = () => {
25+
const { data, fetchNextPage, hasNextPage, isFetching } =
26+
$api.useInfiniteQuery(
27+
"get",
28+
"/posts",
29+
{
30+
params: {
31+
query: {
32+
limit: 10,
33+
},
34+
},
35+
},
36+
{
37+
getNextPageParam: (lastPage) => lastPage.nextPage,
38+
initialPageParam: 0,
39+
}
40+
);
41+
42+
return (
43+
<div>
44+
{data?.pages.map((page, i) => (
45+
<div key={i}>
46+
{page.items.map((post) => (
47+
<div key={post.id}>{post.title}</div>
48+
))}
49+
</div>
50+
))}
51+
{hasNextPage && (
52+
<button onClick={() => fetchNextPage()} disabled={isFetching}>
53+
{isFetching ? "Loading..." : "Load More"}
54+
</button>
55+
)}
56+
</div>
57+
);
58+
};
59+
60+
export const App = () => {
61+
return (
62+
<ErrorBoundary fallbackRender={({ error }) => `Error: ${error.message}`}>
63+
<MyComponent />
64+
</ErrorBoundary>
65+
);
66+
};
67+
```
68+
69+
```ts [src/api.ts]
70+
import createFetchClient from "openapi-fetch";
71+
import createClient from "openapi-react-query";
72+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
73+
74+
const fetchClient = createFetchClient<paths>({
75+
baseUrl: "https://myapi.dev/v1/",
76+
});
77+
export const $api = createClient(fetchClient);
78+
```
79+
80+
:::
81+
82+
## Api
83+
84+
```tsx
85+
const query = $api.useInfiniteQuery(
86+
method,
87+
path,
88+
options,
89+
infiniteQueryOptions,
90+
queryClient
91+
);
92+
```
93+
94+
**Arguments**
95+
96+
- `method` **(required)**
97+
- The HTTP method to use for the request.
98+
- The method is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) for more information.
99+
- `path` **(required)**
100+
- The pathname to use for the request.
101+
- Must be an available path for the given method in your schema.
102+
- The pathname is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) for more information.
103+
- `options`
104+
- The fetch options to use for the request.
105+
- Only required if the OpenApi schema requires parameters.
106+
- The options `params` are used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) for more information.
107+
- `infiniteQueryOptions`
108+
- The original `useInfiniteQuery` options.
109+
- [See more information](https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery)
110+
- `queryClient`
111+
- The original `queryClient` option.
112+
- [See more information](https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery)

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

+80
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import {
55
type UseQueryResult,
66
type UseSuspenseQueryOptions,
77
type UseSuspenseQueryResult,
8+
type UseInfiniteQueryOptions,
9+
type UseInfiniteQueryResult,
810
type QueryClient,
911
type QueryFunctionContext,
1012
type SkipToken,
1113
useMutation,
1214
useQuery,
1315
useSuspenseQuery,
16+
useInfiniteQuery,
1417
} from "@tanstack/react-query";
1518
import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient } from "openapi-fetch";
1619
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers";
@@ -112,6 +115,45 @@ export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMetho
112115
: [InitWithUnknowns<Init>, Options?, QueryClient?]
113116
) => UseSuspenseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
114117

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+
115157
export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
116158
Method extends HttpMethod,
117159
Path extends PathsWithMethod<Paths, Method>,
@@ -129,6 +171,7 @@ export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType =
129171
queryOptions: QueryOptionsFunction<Paths, Media>;
130172
useQuery: UseQueryMethod<Paths, Media>;
131173
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
174+
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
132175
useMutation: UseMutationMethod<Paths, Media>;
133176
}
134177

@@ -156,12 +199,49 @@ export default function createClient<Paths extends {}, Media extends MediaType =
156199
...options,
157200
});
158201

202+
const infiniteQueryOptions = <
203+
Method extends HttpMethod,
204+
Path extends PathsWithMethod<Paths, Method>,
205+
Init extends MaybeOptionalInit<Paths[Path], Method & keyof Paths[Path]>,
206+
Response extends Required<FetchResponse<any, Init, Media>>,
207+
>(
208+
method: Method,
209+
path: Path,
210+
init?: InitWithUnknowns<Init>,
211+
options?: Omit<
212+
UseInfiniteQueryOptions<
213+
Response["data"],
214+
Response["error"],
215+
Response["data"],
216+
number,
217+
QueryKey<Paths, Method, Path>
218+
>,
219+
"queryKey" | "queryFn"
220+
>,
221+
) => ({
222+
queryKey: [method, path, init] as const,
223+
queryFn,
224+
...options,
225+
});
226+
159227
return {
160228
queryOptions,
161229
useQuery: (method, path, ...[init, options, queryClient]) =>
162230
useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
163231
useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>
164232
useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
233+
useInfiniteQuery: (method, path, ...[init, options, queryClient]) => {
234+
const baseOptions = infiniteQueryOptions(method, path, init as InitWithUnknowns<typeof init>, options as any); // TODO: find a way to avoid as any
235+
return useInfiniteQuery(
236+
{
237+
...baseOptions,
238+
initialPageParam: 0,
239+
getNextPageParam: (lastPage: any, allPages: any[], lastPageParam: number, allPageParams: number[]) =>
240+
options?.getNextPageParam?.(lastPage, allPages, lastPageParam, allPageParams) ?? allPages.length,
241+
} as any,
242+
queryClient,
243+
);
244+
},
165245
useMutation: (method, path, options, queryClient) =>
166246
useMutation(
167247
{

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

+52
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,58 @@
44
*/
55

66
export interface paths {
7+
"/paginated-data": {
8+
parameters: {
9+
query?: never;
10+
header?: never;
11+
path?: never;
12+
cookie?: never;
13+
};
14+
get: {
15+
parameters: {
16+
query: {
17+
limit: number;
18+
};
19+
header?: never;
20+
path?: never;
21+
cookie?: never;
22+
};
23+
requestBody?: never;
24+
responses: {
25+
/** @description Successful response */
26+
200: {
27+
headers: {
28+
[name: string]: unknown;
29+
};
30+
content: {
31+
"application/json": {
32+
items?: number[];
33+
nextPage?: number;
34+
};
35+
};
36+
};
37+
/** @description Error response */
38+
500: {
39+
headers: {
40+
[name: string]: unknown;
41+
};
42+
content: {
43+
"application/json": {
44+
code?: number;
45+
message?: string;
46+
};
47+
};
48+
};
49+
};
50+
};
51+
put?: never;
52+
post?: never;
53+
delete?: never;
54+
options?: never;
55+
head?: never;
56+
patch?: never;
57+
trace?: never;
58+
};
759
"/comment": {
860
parameters: {
961
query?: never;

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

+33
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,39 @@ info:
33
title: Test Specification
44
version: "1.0"
55
paths:
6+
/paginated-data:
7+
get:
8+
parameters:
9+
- in: query
10+
name: limit
11+
required: true
12+
schema:
13+
type: integer
14+
responses:
15+
'200':
16+
description: Successful response
17+
content:
18+
application/json:
19+
schema:
20+
type: object
21+
properties:
22+
items:
23+
type: array
24+
items:
25+
type: integer
26+
nextPage:
27+
type: integer
28+
'500':
29+
description: Error response
30+
content:
31+
application/json:
32+
schema:
33+
type: object
34+
properties:
35+
code:
36+
type: integer
37+
message:
38+
type: string
639
/comment:
740
put:
841
requestBody:

0 commit comments

Comments
 (0)