Skip to content

feat(useSuspenseQuery): Add useSuspenseQuery generation #49

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

Merged
merged 2 commits into from
Apr 7, 2024
Merged
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
57 changes: 41 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
$ npm install -D @7nohe/openapi-react-query-codegen
```

Register the command to the `scripts` property in your package.json file.
Register the command to the `scripts` property in your package.json file.

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


## Usage

```
Expand Down Expand Up @@ -67,17 +66,18 @@ $ openapi-rq -i ./petstore.yaml
```
- openapi
- queries
- index.ts <- custom react hooks
- index.ts <- main file that exports common types, variables, and hooks
- common.ts <- common types
- queries.ts <- generated query hooks
- suspenses.ts <- generated suspense hooks
- requests <- output code generated by OpenAPI Typescript Codegen
```

### In your app

```tsx
// App.tsx
import {
usePetServiceFindPetsByStatus,
} from "../openapi/queries";
import { usePetServiceFindPetsByStatus } from "../openapi/queries";
function App() {
const { data } = usePetServiceFindPetsByStatus({ status: ["available"] });

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

```tsx
import { useQuery } from "@tanstack/react-query";
import { PetService } from '../openapi/requests/services/PetService';
import {
usePetServiceFindPetsByStatusKey,
} from "../openapi/queries";
import { PetService } from "../openapi/requests/services/PetService";
import { usePetServiceFindPetsByStatusKey } from "../openapi/queries";

function App() {
// You can still use the auto-generated query key
const { data } = useQuery({
queryKey: [usePetServiceFindPetsByStatusKey],
queryFn: () => {
// Do something here
return PetService.findPetsByStatus(['available']);
}
});
return PetService.findPetsByStatus(["available"]);
},
});

return <div className="App">{/* .... */}</div>;
}

export default App;
```

You can also use suspense hooks.

```tsx
// App.tsx
import { useDefaultClientFindPetsSuspense } from "../openapi/queries/suspense";
function ChildComponent() {
const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });

return (
<div className="App">
{/* .... */}
</div>
<ul>
{data?.map((pet, index) => (
<li key={pet.id}>{pet.name}</li>
))}
</ul>
);
}

function ParentComponent() {
return (
<>
<Suspense fallback={<>loading...</>}>
<ChildComponent />
</Suspense>
</>
);
}

export default App;
```

## License

MIT
8 changes: 7 additions & 1 deletion examples/react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "../openapi/queries";
import { useState } from "react";
import { queryClient } from "./queryClient";
import { SuspenseParent } from "./components/SuspenseParent";

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

const { mutate: addPet } = useDefaultClientAddPet();

Expand Down Expand Up @@ -60,6 +62,10 @@ function App() {
>
Create a pet
</button>
<div>
<h1>Suspense Components</h1>
<SuspenseParent />
</div>
</div>
);
}
Expand Down
17 changes: 17 additions & 0 deletions examples/react-app/src/components/SuspenseChild.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useDefaultClientFindPetsSuspense } from "../../openapi/queries/suspense";

export const SuspenseChild = () => {
const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });

if (!Array.isArray(data)) {
return <div>Error!</div>;
}

return (
<ul>
{data?.map((pet, index) => (
<li key={pet.id}>{pet.name}</li>
))}
</ul>
);
};
12 changes: 12 additions & 0 deletions examples/react-app/src/components/SuspenseParent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Suspense } from "react";
import { SuspenseChild } from "./SuspenseChild";

export const SuspenseParent = () => {
return (
<>
<Suspense fallback={<>loading...</>}>
<SuspenseChild />
</Suspense>
</>
);
};
49 changes: 49 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,56 @@
import { type PathLike } from "fs";
import { stat } from "fs/promises";
import ts, { JSDocComment, NodeArray, SourceFile } from "typescript";

export const TData = ts.factory.createIdentifier("TData");
export const TError = ts.factory.createIdentifier("TError");
export const TContext = ts.factory.createIdentifier("TContext");

export const queryKeyGenericType =
ts.factory.createTypeReferenceNode("TQueryKey");
export const queryKeyConstraint = ts.factory.createTypeReferenceNode("Array", [
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
]);

export const capitalizeFirstLetter = (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};

export const lowercaseFirstLetter = (str: string) => {
return str.charAt(0).toLowerCase() + str.slice(1);
};

export const getNameFromMethod = (
method: ts.MethodDeclaration,
node: ts.SourceFile
) => {
return method.name.getText(node);
};

export type MethodDescription = {
className: string;
node: SourceFile;
method: ts.MethodDeclaration;
methodBlock: ts.Block;
httpMethodName: string;
jsDoc: (string | NodeArray<JSDocComment> | undefined)[];
isDeprecated: boolean;
};

export async function exists(f: PathLike) {
try {
await stat(f);
return true;
} catch {
return false;
}
}

const Common = "Common";

export function BuildCommonTypeName(name: string | ts.Identifier) {
if (typeof name === "string") {
return ts.factory.createIdentifier(`${Common}.${name}`);
}
return ts.factory.createIdentifier(`${Common}.${name.text}`);
}
Loading