Skip to content

Commit d510d54

Browse files
committed
Working function
1 parent af0e72f commit d510d54

File tree

2 files changed

+216
-1
lines changed

2 files changed

+216
-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

+104-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
type UseMutationResult,
44
type UseQueryOptions,
55
type UseQueryResult,
6+
type UseInfiniteQueryOptions,
7+
type UseInfiniteQueryResult,
68
type UseSuspenseQueryOptions,
79
type UseSuspenseQueryResult,
810
type QueryClient,
@@ -11,8 +13,9 @@ import {
1113
useMutation,
1214
useQuery,
1315
useSuspenseQuery,
16+
useInfiniteQuery,
1417
} from "@tanstack/react-query";
15-
import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient } from "openapi-fetch";
18+
import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient, DefaultParamsOption } from "openapi-fetch";
1619
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers";
1720

1821
// Helper type to dynamically infer the type from the `select` property
@@ -90,6 +93,45 @@ export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>,
9093
: [InitWithUnknowns<Init>, Options?, QueryClient?]
9194
) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
9295

96+
export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
97+
Method extends HttpMethod,
98+
Path extends PathsWithMethod<Paths, Method>,
99+
Init extends MaybeOptionalInit<Paths[Path], Method>,
100+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
101+
>(
102+
method: Method,
103+
url: Path,
104+
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
105+
? [
106+
InitWithUnknowns<Init>?,
107+
Omit<
108+
UseInfiniteQueryOptions<
109+
Response["data"],
110+
Response["error"],
111+
Response["data"],
112+
number,
113+
QueryKey<Paths, Method, Path>
114+
>,
115+
"queryKey" | "queryFn"
116+
>?,
117+
QueryClient?,
118+
]
119+
: [
120+
InitWithUnknowns<Init>,
121+
Omit<
122+
UseInfiniteQueryOptions<
123+
Response["data"],
124+
Response["error"],
125+
Response["data"],
126+
number,
127+
QueryKey<Paths, Method, Path>
128+
>,
129+
"queryKey" | "queryFn"
130+
>?,
131+
QueryClient?,
132+
]
133+
) => UseInfiniteQueryResult<Response["data"], Response["error"]>;
134+
93135
export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
94136
Method extends HttpMethod,
95137
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,72 @@ 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: async ({
224+
queryKey: [method, path, init],
225+
pageParam = 0,
226+
signal,
227+
}: QueryFunctionContext<QueryKey<Paths, Method, Path>, number>) => {
228+
const mth = method.toUpperCase() as Uppercase<typeof method>;
229+
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
230+
const mergedInit = {
231+
...init,
232+
signal,
233+
params: {
234+
...(init?.params || {}),
235+
query: {
236+
...(init?.params as { query?: DefaultParamsOption })?.query,
237+
cursor: pageParam,
238+
},
239+
},
240+
};
241+
242+
const { data, error } = await fn(path, mergedInit as any);
243+
if (error) {
244+
throw error;
245+
}
246+
return data;
247+
},
248+
...options,
249+
});
250+
159251
return {
160252
queryOptions,
161253
useQuery: (method, path, ...[init, options, queryClient]) =>
162254
useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
163255
useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>
164256
useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
257+
useInfiniteQuery: (method, path, ...[init, options, queryClient]) => {
258+
const baseOptions = infiniteQueryOptions(method, path, init as InitWithUnknowns<typeof init>, options as any); // TODO: find a way to avoid as any
259+
return useInfiniteQuery(
260+
{
261+
...baseOptions,
262+
getNextPageParam: (lastPage: any, allPages: any[], lastPageParam: number, allPageParams: number[]) =>
263+
options?.getNextPageParam?.(lastPage, allPages, lastPageParam, allPageParams) ?? allPages.length,
264+
} as any,
265+
queryClient,
266+
);
267+
},
165268
useMutation: (method, path, options, queryClient) =>
166269
useMutation(
167270
{

0 commit comments

Comments
 (0)