Skip to content

Commit d1976e0

Browse files
authored
feat!: Add support for skipToken (#289)
* Add changelogs in prettier ignore * Remove generateReactQueryFunctions This is a legacy duplicate with the new queryFn * Fix let keyword in the fetcher template * Fix types issue on `deepMerge` * Make sure to not generate an empty description * Add type hint to avoid over generating * Add support for SkipToken * Update documentation * Update example
1 parent af5b66f commit d1976e0

18 files changed

+19283
-12162
lines changed

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
pnpm-lock.yaml
1+
pnpm-lock.yaml
2+
**/CHANGELOG.md

README.md

Lines changed: 65 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -79,85 +79,80 @@
7979

8080
- Wire up the `QueryClient` as described [here](https://tanstack.com/query/v4/docs/adapters/react-query).
8181

82-
## Philosophy
83-
84-
In software development, communication between components and documentation around it is often no fun.
85-
86-
GraphQL did resolve this by making documentation a part of the tooling (introspection), sadly this is often harder with REST APIs. OpenAPI can be an amazing tool, if, and only if the documentation (spec) and the actual implementation are aligned!
87-
88-
### Backend side
89-
90-
There are two different approaches:
91-
92-
1. The OpenAPI spec is generated from the code (**code first**)
93-
2. The code is generated from the OpenAPI spec (**spec first**)
94-
95-
In either case, there needs to be an integration with the type system of the language, so everything is connected, and as we remove or update something that impacts the final response, this is **automatically** reflected!
96-
97-
This library has chosen the second approach, **spec first**. By doing so, your documentation is not your final (boring) task on the list, but the first and exciting one when adding new functionality! Indeed, you can’t start coding without generating your types (models & controllers) from the specs.
98-
99-
This has multiple benefits:
100-
101-
- You can take your time to think about your API before writing any code!
102-
- You can discuss the API with your team (and discover API design problems earlier)
103-
- You can generate all your validation rules
104-
105-
For example, if you add this object to your schema:
106-
107-
```yaml
108-
SignUpInput:
109-
type: object
110-
properties:
111-
email:
112-
type: string
113-
format: email
114-
maxLength: 255
115-
password:
116-
type: string
117-
maxLength: 255
118-
firstName:
119-
type: string
120-
pattern: ^[0-9a-zA-Z]*$
121-
maxLength: 255
122-
lastName:
123-
type: string
124-
pattern: ^[0-9a-zA-Z]*$
125-
maxLength: 255
126-
required:
127-
- email
128-
- password
129-
- firstName
130-
- lastName
131-
```
82+
## Usage
83+
84+
### React Query components
13285

133-
OpenAPI Codegen will be able to generate all the relevant validation (or at least give you the choice to do it).
86+
Using [https://api.apis.guru/v2/specs/giphy.com/1.0/openapi.yaml](giphy specs) as example
13487

135-
> **Note**
136-
> You can also attach any custom logic by using the `x-*` tag, the possibilities are endless!
88+
This will generate lot of ready-to-use hooks like:
13789

138-
### Frontend side
90+
- `useRandomGif` -> Wrapper around `useQuery` with injected types
91+
- `useSuspenseRandomGif` -> Same but with `useSuspense`
13992

140-
Having to reverse engineer a backend response is the least productive/fun task ever! However, given a nice OpenAPI specs, we can actually generate nicely typed code for you that lets you interact with your API in a safe manner.
93+
And you will have some `useMutation` if the api expose some (not the case with this example)
14194

142-
Taking React as example, calling an API can be as simple as this: _(this hooks are using **Tanstack Query** under the hood)_
95+
Here an example of usage of this generated api:
14396

14497
```tsx
145-
import { useListPets } from "./petStore/petStoreComponents"; // <- output from openapi-codegen
98+
import { useRandomGif } from "./giphy";
14699
147-
const Example = () => {
148-
const { data, loading, error } = useListPets();
100+
export function GifExplorer() {
101+
const [tag, setTag] = useState("");
102+
const { data, error, isError, isPending } = useRandomGif({
103+
queryParams: {
104+
tag,
105+
},
106+
});
107+
108+
return (
109+
<div>
110+
<input value={tag} onChange={(e) => setTag(e.currentTarget.value)} />
111+
{isPending ? (
112+
<div>Loading…</div>
113+
) : isError ? (
114+
<div>
115+
<pre>{error.payload ?? "Unknown error"}</pre>
116+
</div>
117+
) : (
118+
// This is typed!
119+
<img src={data.data?.url} />
120+
)}
121+
</div>
122+
);
123+
}
124+
```
149125
150-
// `data` is fully typed and have all documentation from OpenAPI
151-
};
126+
This also support `reactQuery.skipToken` to stay type-safe when you are waiting for params:
127+
128+
```diff
129+
+ import { skipToken } from "@tanstack/react-query";
130+
131+
- const { data, error, isError, isPending } = useRandomGif({
132+
- queryParams: {
133+
- tag,
134+
- },
135+
- });
136+
+ const { data, error, isError, isPending } = useRandomGif(
137+
+ tag
138+
+ ? skipToken
139+
+ : {
140+
+ queryParams: {
141+
+ tag,
142+
+ },
143+
+ }
144+
+ );
152145
```
153146
154-
> **Note**
155-
> You can also check this blog post about using generated hooks in React https://xata.io/blog/openapi-typesafe-react-query-hooks
147+
You can also use directly the queryFn for more advanced use cases:
156148
157-
And since this generated from the specs, everything is safe at build time!
149+
```tsx
150+
import { randomGifQuery } from "./giphy/giphyComponents";
151+
152+
const queryClient = new QueryClient();
158153
159-
> **Note**
160-
> If you can’t trust your backend, some runtime validation can be useful to avoid surprises in production 😅
154+
queryClient.fetchQuery(randomGifQuery({}));
155+
```
161156
162157
## Configuration
163158
@@ -224,7 +219,7 @@ output: `{namespace}Schemas.ts`
224219
225220
#### **generateFetchers** (frontend)
226221
227-
generate all fetchers with types for your specification _needs schemafiles_
222+
generate all fetchers with types for your specification
228223
229224
```ts
230225
await generateFetchers(context, {
@@ -248,32 +243,14 @@ await generateReactQueryComponents(context, {
248243
249244
output: `{namespace}Components.ts`
250245
251-
#### **generateReactQueryFunctions** (frontend)
246+
This is also generate a query function that can be used in most of React Query functions.
252247
253-
generate all React Query Functions used for e.g. React-Router 6.6.0+ loader functions
248+
Example with `queryClient.fetchQuery` (data loader case):
254249
255250
```ts
256-
await generateReactQueryFunctions(context, {
257-
filenamePrefix,
258-
schemasFiles,
259-
});
251+
await queryClient.fetchQuery(getYourQueryNameQuery({}));
260252
```
261253
262-
output: `{namespace}Functions.ts`
263-
264-
example usage in react-route-loader:
265-
266-
```ts
267-
export const routeLoader =
268-
(queryClient: QueryClient) =>
269-
async ({ params }: MyParams) =>
270-
await queryClient.fetchQuery(...getYourQueryNameQuery({}), {
271-
/*options*/
272-
});
273-
```
274-
275-
_more infos: https://reactrouter.com/en/main/guides/data-libs_
276-
277254
You can import any generator into the `to` section, those can be the ones provided by this project or your own custom ones. You have full control of what you are generating!
278255
279256
Have fun!

examples/frontend/src/App.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { useState } from "react";
2-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2+
import {
3+
QueryClient,
4+
QueryClientProvider,
5+
skipToken,
6+
} from "@tanstack/react-query";
37
import { AuthProvider } from "./Auth";
48
import { useSearchUsers } from "./github/githubComponents";
59

@@ -18,12 +22,11 @@ function App() {
1822
function Users() {
1923
const [query, setQuery] = useState("");
2024
const { data, error, isPending } = useSearchUsers(
21-
{
22-
queryParams: { q: query },
23-
},
24-
{
25-
enabled: Boolean(query),
26-
}
25+
query
26+
? {
27+
queryParams: { q: query },
28+
}
29+
: skipToken
2730
);
2831

2932
if (error) {

0 commit comments

Comments
 (0)