Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(openapi-react-query): support for useInfiniteQuery() #1881

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions docs/openapi-react-query/use-infinite-query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: useInfiniteQuery
---

# {{ $frontmatter.title }}

The `useInfiniteQuery` method allows you to use the original [useInfiniteQuery](https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries)

- The result is the same as the original function.
- The `queryKey` is `[method, path, params]`.
- `data` and `error` are fully typed.
- You can pass infinite query options as fourth parameter.

::: tip
You can find more information about `useInfiniteQuery` on the [@tanstack/react-query documentation](https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries).
:::

## Example

::: code-group

```tsx [src/app.tsx]
import { $api } from "./api";
const PostList = () => {
const { data, fetchNextPage, hasNextPage, isFetching } =
$api.useInfiniteQuery(
"get",
"/posts",
{
params: {
query: {
limit: 10,
},
},
},
{
getNextPageParam: (lastPage) => lastPage.nextPage,
initialPageParam: 0,
}
);

return (
<div>
{data?.pages.map((page, i) => (
<div key={i}>
{page.items.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
))}
{hasNextPage && (
<button onClick={() => fetchNextPage()} disabled={isFetching}>
{isFetching ? "Loading..." : "Load More"}
</button>
)}
</div>
);
};

export const App = () => {
return (
<ErrorBoundary fallbackRender={({ error }) => `Error: ${error.message}`}>
<MyComponent />
</ErrorBoundary>
);
};
```

```ts [src/api.ts]
import createFetchClient from "openapi-fetch";
import createClient from "openapi-react-query";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const fetchClient = createFetchClient<paths>({
baseUrl: "https://myapi.dev/v1/",
});
export const $api = createClient(fetchClient);
```

:::

## Api

```tsx
const query = $api.useInfiniteQuery(
method,
path,
options,
infiniteQueryOptions,
queryClient
);
```

**Arguments**

- `method` **(required)**
- The HTTP method to use for the request.
- The method is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) for more information.
- `path` **(required)**
- The pathname to use for the request.
- Must be an available path for the given method in your schema.
- The pathname is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) for more information.
- `options`
- The fetch options to use for the request.
- 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`
- The original `useInfiniteQuery` options.
- [See more information](https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery)
- `queryClient`
- The original `queryClient` option.
- [See more information](https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery)
80 changes: 80 additions & 0 deletions packages/openapi-react-query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import {
type UseQueryResult,
type UseSuspenseQueryOptions,
type UseSuspenseQueryResult,
type UseInfiniteQueryOptions,
type UseInfiniteQueryResult,
type QueryClient,
type QueryFunctionContext,
type SkipToken,
useMutation,
useQuery,
useSuspenseQuery,
useInfiniteQuery,
} from "@tanstack/react-query";
import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient } from "openapi-fetch";
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers";
Expand Down Expand Up @@ -84,6 +87,45 @@ export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMetho
: [InitWithUnknowns<Init>, Options?, QueryClient?]
) => UseSuspenseQueryResult<Response["data"], Response["error"]>;

export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends MaybeOptionalInit<Paths[Path], Method>,
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
>(
method: Method,
url: Path,
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
? [
InitWithUnknowns<Init>?,
Omit<
UseInfiniteQueryOptions<
Response["data"],
Response["error"],
Response["data"],
number,
QueryKey<Paths, Method, Path>
>,
"queryKey" | "queryFn"
>?,
QueryClient?,
]
: [
InitWithUnknowns<Init>,
Omit<
UseInfiniteQueryOptions<
Response["data"],
Response["error"],
Response["data"],
number,
QueryKey<Paths, Method, Path>
>,
"queryKey" | "queryFn"
>?,
QueryClient?,
]
) => UseInfiniteQueryResult<Response["data"], Response["error"]>;

export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Expand All @@ -101,6 +143,7 @@ export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType =
queryOptions: QueryOptionsFunction<Paths, Media>;
useQuery: UseQueryMethod<Paths, Media>;
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
useMutation: UseMutationMethod<Paths, Media>;
}

Expand Down Expand Up @@ -128,12 +171,49 @@ export default function createClient<Paths extends {}, Media extends MediaType =
...options,
});

const infiniteQueryOptions = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends MaybeOptionalInit<Paths[Path], Method & keyof Paths[Path]>,
Response extends Required<FetchResponse<any, Init, Media>>,
>(
method: Method,
path: Path,
init?: InitWithUnknowns<Init>,
options?: Omit<
UseInfiniteQueryOptions<
Response["data"],
Response["error"],
Response["data"],
number,
QueryKey<Paths, Method, Path>
>,
"queryKey" | "queryFn"
>,
) => ({
queryKey: [method, path, init] as const,
queryFn,
...options,
});

return {
queryOptions,
useQuery: (method, path, ...[init, options, queryClient]) =>
useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>
useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
useInfiniteQuery: (method, path, ...[init, options, queryClient]) => {
const baseOptions = infiniteQueryOptions(method, path, init as InitWithUnknowns<typeof init>, options as any); // TODO: find a way to avoid as any
return useInfiniteQuery(
{
...baseOptions,
initialPageParam: 0,
getNextPageParam: (lastPage: any, allPages: any[], lastPageParam: number, allPageParams: number[]) =>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add getPreviousPageParam here too or do we get it by default via baseOptions ?

options?.getNextPageParam?.(lastPage, allPages, lastPageParam, allPageParams) ?? allPages.length,
} as any,
queryClient,
);
},
useMutation: (method, path, options, queryClient) =>
useMutation(
{
Expand Down
52 changes: 52 additions & 0 deletions packages/openapi-react-query/test/fixtures/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,58 @@
*/

export interface paths {
"/paginated-data": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: {
parameters: {
query: {
limit: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
items?: number[];
nextPage?: number;
};
};
};
/** @description Error response */
500: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
code?: number;
message?: string;
};
};
};
};
};
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/comment": {
parameters: {
query?: never;
Expand Down
33 changes: 33 additions & 0 deletions packages/openapi-react-query/test/fixtures/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,39 @@ info:
title: Test Specification
version: "1.0"
paths:
/paginated-data:
get:
parameters:
- in: query
name: limit
required: true
schema:
type: integer
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: integer
nextPage:
type: integer
'500':
description: Error response
content:
application/json:
schema:
type: object
properties:
code:
type: integer
message:
type: string
/comment:
put:
requestBody:
Expand Down
Loading
Loading