Skip to content

Commit 4374671

Browse files
authored
feat(useSuspenseQuery): Add useSuspenseQuery generation (#49)
1 parent e9a9ee8 commit 4374671

File tree

12 files changed

+750
-172
lines changed

12 files changed

+750
-172
lines changed

README.md

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
$ npm install -D @7nohe/openapi-react-query-codegen
1515
```
1616

17-
Register the command to the `scripts` property in your package.json file.
17+
Register the command to the `scripts` property in your package.json file.
1818

1919
```json
2020
{
@@ -30,7 +30,6 @@ You can also run the command without installing it in your project using the npx
3030
$ npx --package @7nohe/openapi-react-query-codegen openapi-rq -i ./petstore.yaml -c axios
3131
```
3232

33-
3433
## Usage
3534

3635
```
@@ -67,17 +66,18 @@ $ openapi-rq -i ./petstore.yaml
6766
```
6867
- openapi
6968
- queries
70-
- index.ts <- custom react hooks
69+
- index.ts <- main file that exports common types, variables, and hooks
70+
- common.ts <- common types
71+
- queries.ts <- generated query hooks
72+
- suspenses.ts <- generated suspense hooks
7173
- requests <- output code generated by OpenAPI Typescript Codegen
7274
```
7375

7476
### In your app
7577

7678
```tsx
7779
// App.tsx
78-
import {
79-
usePetServiceFindPetsByStatus,
80-
} from "../openapi/queries";
80+
import { usePetServiceFindPetsByStatus } from "../openapi/queries";
8181
function App() {
8282
const { data } = usePetServiceFindPetsByStatus({ status: ["available"] });
8383

@@ -100,30 +100,55 @@ You can also use pure TS clients.
100100

101101
```tsx
102102
import { useQuery } from "@tanstack/react-query";
103-
import { PetService } from '../openapi/requests/services/PetService';
104-
import {
105-
usePetServiceFindPetsByStatusKey,
106-
} from "../openapi/queries";
103+
import { PetService } from "../openapi/requests/services/PetService";
104+
import { usePetServiceFindPetsByStatusKey } from "../openapi/queries";
107105

108106
function App() {
109107
// You can still use the auto-generated query key
110108
const { data } = useQuery({
111109
queryKey: [usePetServiceFindPetsByStatusKey],
112110
queryFn: () => {
113111
// Do something here
114-
return PetService.findPetsByStatus(['available']);
115-
}
116-
});
112+
return PetService.findPetsByStatus(["available"]);
113+
},
114+
});
115+
116+
return <div className="App">{/* .... */}</div>;
117+
}
118+
119+
export default App;
120+
```
121+
122+
You can also use suspense hooks.
123+
124+
```tsx
125+
// App.tsx
126+
import { useDefaultClientFindPetsSuspense } from "../openapi/queries/suspense";
127+
function ChildComponent() {
128+
const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });
117129

118130
return (
119-
<div className="App">
120-
{/* .... */}
121-
</div>
131+
<ul>
132+
{data?.map((pet, index) => (
133+
<li key={pet.id}>{pet.name}</li>
134+
))}
135+
</ul>
136+
);
137+
}
138+
139+
function ParentComponent() {
140+
return (
141+
<>
142+
<Suspense fallback={<>loading...</>}>
143+
<ChildComponent />
144+
</Suspense>
145+
</>
122146
);
123147
}
124148

125149
export default App;
126150
```
127151

128152
## License
153+
129154
MIT

examples/react-app/src/App.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "../openapi/queries";
99
import { useState } from "react";
1010
import { queryClient } from "./queryClient";
11+
import { SuspenseParent } from "./components/SuspenseParent";
1112

1213
function App() {
1314
const [tags, _setTags] = useState<string[]>([]);
@@ -19,7 +20,8 @@ function App() {
1920
// this defaults to any - here we are showing how to override the type
2021
// Note - this is marked as deprecated in the OpenAPI spec and being passed to the client
2122
const { data: notDefined } = useDefaultClientGetNotDefined<undefined>();
22-
const { mutate: mutateNotDefined } = useDefaultClientPostNotDefined<undefined>();
23+
const { mutate: mutateNotDefined } =
24+
useDefaultClientPostNotDefined<undefined>();
2325

2426
const { mutate: addPet } = useDefaultClientAddPet();
2527

@@ -60,6 +62,10 @@ function App() {
6062
>
6163
Create a pet
6264
</button>
65+
<div>
66+
<h1>Suspense Components</h1>
67+
<SuspenseParent />
68+
</div>
6369
</div>
6470
);
6571
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useDefaultClientFindPetsSuspense } from "../../openapi/queries/suspense";
2+
3+
export const SuspenseChild = () => {
4+
const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });
5+
6+
if (!Array.isArray(data)) {
7+
return <div>Error!</div>;
8+
}
9+
10+
return (
11+
<ul>
12+
{data?.map((pet, index) => (
13+
<li key={pet.id}>{pet.name}</li>
14+
))}
15+
</ul>
16+
);
17+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Suspense } from "react";
2+
import { SuspenseChild } from "./SuspenseChild";
3+
4+
export const SuspenseParent = () => {
5+
return (
6+
<>
7+
<Suspense fallback={<>loading...</>}>
8+
<SuspenseChild />
9+
</Suspense>
10+
</>
11+
);
12+
};

src/common.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,56 @@
1+
import { type PathLike } from "fs";
2+
import { stat } from "fs/promises";
3+
import ts, { JSDocComment, NodeArray, SourceFile } from "typescript";
4+
5+
export const TData = ts.factory.createIdentifier("TData");
6+
export const TError = ts.factory.createIdentifier("TError");
7+
export const TContext = ts.factory.createIdentifier("TContext");
8+
9+
export const queryKeyGenericType =
10+
ts.factory.createTypeReferenceNode("TQueryKey");
11+
export const queryKeyConstraint = ts.factory.createTypeReferenceNode("Array", [
12+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
13+
]);
14+
115
export const capitalizeFirstLetter = (str: string) => {
216
return str.charAt(0).toUpperCase() + str.slice(1);
317
};
418

519
export const lowercaseFirstLetter = (str: string) => {
620
return str.charAt(0).toLowerCase() + str.slice(1);
721
};
22+
23+
export const getNameFromMethod = (
24+
method: ts.MethodDeclaration,
25+
node: ts.SourceFile
26+
) => {
27+
return method.name.getText(node);
28+
};
29+
30+
export type MethodDescription = {
31+
className: string;
32+
node: SourceFile;
33+
method: ts.MethodDeclaration;
34+
methodBlock: ts.Block;
35+
httpMethodName: string;
36+
jsDoc: (string | NodeArray<JSDocComment> | undefined)[];
37+
isDeprecated: boolean;
38+
};
39+
40+
export async function exists(f: PathLike) {
41+
try {
42+
await stat(f);
43+
return true;
44+
} catch {
45+
return false;
46+
}
47+
}
48+
49+
const Common = "Common";
50+
51+
export function BuildCommonTypeName(name: string | ts.Identifier) {
52+
if (typeof name === "string") {
53+
return ts.factory.createIdentifier(`${Common}.${name}`);
54+
}
55+
return ts.factory.createIdentifier(`${Common}.${name.text}`);
56+
}

0 commit comments

Comments
 (0)