|
1 | 1 | # OpenAPI React Query Codegen |
2 | 2 |
|
3 | | -> Node.js library that generates [React Query (also called TanStack Query)](https://tanstack.com/query) hooks based on an OpenAPI specification file. |
| 3 | +> Code generator for creating [React Query (also known as TanStack Query)](https://tanstack.com/query) hooks based on your OpenAPI schema. |
4 | 4 |
|
5 | 5 | [](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen) |
6 | 6 |
|
|
10 | 10 | - Generates custom functions that use React Query's `ensureQueryData` and `prefetchQuery` functions |
11 | 11 | - Generates query keys and functions for query caching |
12 | 12 | - Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) |
13 | | - |
14 | | -## Installation |
15 | | - |
16 | | -``` |
17 | | -$ npm install -D @7nohe/openapi-react-query-codegen |
18 | | -``` |
19 | | - |
20 | | -Register the command to the `scripts` property in your package.json file. |
21 | | - |
22 | | -```json |
23 | | -{ |
24 | | - "scripts": { |
25 | | - "codegen": "openapi-rq -i ./petstore.yaml -c @hey-api/client-fetch" |
26 | | - } |
27 | | -} |
28 | | -``` |
29 | | - |
30 | | -You can also run the command without installing it in your project using the npx command. |
31 | | - |
32 | | -```bash |
33 | | -$ npx --package @7nohe/openapi-react-query-codegen openapi-rq -i ./petstore.yaml -c @hey-api/client-fetch |
34 | | -``` |
35 | | - |
36 | | -## Usage |
37 | | - |
38 | | -``` |
39 | | -$ openapi-rq --help |
40 | | -
|
41 | | -Usage: openapi-rq [options] |
42 | | -
|
43 | | -Generate React Query code based on OpenAPI |
44 | | -
|
45 | | -Options: |
46 | | - -V, --version output the version number |
47 | | - -i, --input <value> OpenAPI specification, can be a path, url or string content (required) |
48 | | - -o, --output <value> Output directory (default: "openapi") |
49 | | - -c, --client <value> HTTP client to generate (choices: "@hey-api/client-fetch", "@hey-api/client-axios", default: "@hey-api/client-fetch") |
50 | | - --format <value> Process output folder with formatter? (choices: "biome", "prettier") |
51 | | - --lint <value> Process output folder with linter? (choices: "biome", "eslint") |
52 | | - --operationId Use operation ID to generate operation names? |
53 | | - --serviceResponse <value> Define shape of returned value from service calls (choices: "body", "response", default: "body") |
54 | | - --enums <value> Generate JavaScript objects from enum definitions? (choices: "javascript", "typescript") |
55 | | - --useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object |
56 | | - --debug Run in debug mode? |
57 | | - --noSchemas Disable generating JSON schemas |
58 | | - --schemaType <value> Type of JSON schema [Default: 'json'] (choices: "form", "json") |
59 | | - --pageParam <value> Name of the query parameter used for pagination (default: "page") |
60 | | - --nextPageParam <value> Name of the response parameter used for next page (default: "nextPage") |
61 | | - --initialPageParam <value> Initial page value to query (default: "initialPageParam") |
62 | | - -h, --help display help for command |
63 | | -``` |
64 | | - |
65 | | -### Example Usage |
66 | | - |
67 | | -#### Command |
68 | | - |
69 | | -``` |
70 | | -$ openapi-rq -i ./petstore.yaml |
71 | | -``` |
72 | | - |
73 | | -#### Output directory structure |
74 | | - |
75 | | -``` |
76 | | -- openapi |
77 | | - - queries |
78 | | - - index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks |
79 | | - - common.ts <- common types |
80 | | - - ensureQueryData.ts <- generated ensureQueryData functions |
81 | | - - queries.ts <- generated query hooks |
82 | | - - suspenses.ts <- generated suspense hooks |
83 | | - - prefetch.ts <- generated prefetch functions learn more about prefetching in in link below |
84 | | - - requests <- output code generated by @hey-api/openapi-ts |
85 | | -``` |
86 | | - |
87 | | -- [Prefetching docs](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#prefetching-and-dehydrating-data) |
88 | | - |
89 | | -#### In your app |
90 | | - |
91 | | -##### Using the generated hooks |
92 | | - |
93 | | -```tsx |
94 | | -// App.tsx |
95 | | -import { useFindPets } from "../openapi/queries"; |
96 | | -function App() { |
97 | | - const { data } = useFindPets(); |
98 | | - |
99 | | - return ( |
100 | | - <div className="App"> |
101 | | - <h1>Pet List</h1> |
102 | | - <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul> |
103 | | - </div> |
104 | | - ); |
105 | | -} |
106 | | - |
107 | | -export default App; |
108 | | -``` |
109 | | - |
110 | | -##### Using the generated typescript client |
111 | | - |
112 | | -```tsx |
113 | | -import { useQuery } from "@tanstack/react-query"; |
114 | | -import { findPets } from "../openapi/requests/services.gen"; |
115 | | -import { useFindPetsKey } from "../openapi/queries"; |
116 | | - |
117 | | -function App() { |
118 | | - // You can still use the auto-generated query key |
119 | | - const { data } = useQuery({ |
120 | | - queryKey: [useFindPetsKey], |
121 | | - queryFn: () => { |
122 | | - // Do something here |
123 | | - return findPets(); |
124 | | - }, |
125 | | - }); |
126 | | - |
127 | | - return <div className="App">{/* .... */}</div>; |
128 | | -} |
129 | | - |
130 | | -export default App; |
131 | | -``` |
132 | | - |
133 | | -##### Using Suspense Hooks |
134 | | - |
135 | | -```tsx |
136 | | -// App.tsx |
137 | | -import { useFindPetsSuspense } from "../openapi/queries/suspense"; |
138 | | -function ChildComponent() { |
139 | | - const { data } = useFindPetsSuspense({ |
140 | | - query: { tags: [], limit: 10 }, |
141 | | - }); |
142 | | - |
143 | | - return <ul>{data?.map((pet, index) => <li key={pet.id}>{pet.name}</li>)}</ul>; |
144 | | -} |
145 | | - |
146 | | -function ParentComponent() { |
147 | | - return ( |
148 | | - <> |
149 | | - <Suspense fallback={<>loading...</>}> |
150 | | - <ChildComponent /> |
151 | | - </Suspense> |
152 | | - </> |
153 | | - ); |
154 | | -} |
155 | | - |
156 | | -function App() { |
157 | | - return ( |
158 | | - <div className="App"> |
159 | | - <h1>Pet List</h1> |
160 | | - <ParentComponent /> |
161 | | - </div> |
162 | | - ); |
163 | | -} |
164 | | - |
165 | | -export default App; |
166 | | -``` |
167 | | - |
168 | | -##### Using Mutation hooks |
169 | | - |
170 | | -```tsx |
171 | | -// App.tsx |
172 | | -import { useAddPet } from "../openapi/queries"; |
173 | | - |
174 | | -function App() { |
175 | | - const { mutate } = useAddPet(); |
176 | | - |
177 | | - const handleAddPet = () => { |
178 | | - mutate({ body: { name: "Fluffy" } }); |
179 | | - }; |
180 | | - |
181 | | - return ( |
182 | | - <div className="App"> |
183 | | - <h1>Add Pet</h1> |
184 | | - <button onClick={handleAddPet}>Add Pet</button> |
185 | | - </div> |
186 | | - ); |
187 | | -} |
188 | | - |
189 | | -export default App; |
190 | | -``` |
191 | | - |
192 | | -##### Invalidating queries after mutation |
193 | | - |
194 | | -Invalidating queries after a mutation is important to ensure the cache is updated with the new data. This is done by calling the `queryClient.invalidateQueries` function with the query key used by the query hook. |
195 | | - |
196 | | -Learn more about invalidating queries [here](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation). |
197 | | - |
198 | | -To ensure the query key is created the same way as the query hook, you can use the query key function exported by the generated query hooks. |
199 | | - |
200 | | -```tsx |
201 | | -import { |
202 | | - useFindPetsByStatus, |
203 | | - useAddPet, |
204 | | - UseFindPetsByStatusKeyFn, |
205 | | -} from "../openapi/queries"; |
206 | | - |
207 | | -// App.tsx |
208 | | -function App() { |
209 | | - const [status, setStatus] = React.useState(["available"]); |
210 | | - const { data } = useFindPetsByStatus({ status }); |
211 | | - const { mutate } = useAddPet({ |
212 | | - onSuccess: () => { |
213 | | - queryClient.invalidateQueries({ |
214 | | - // Call the query key function to get the query key |
215 | | - // This is important to ensure the query key is created the same way as the query hook |
216 | | - // This insures the cache is invalidated correctly and is typed correctly |
217 | | - queryKey: [UseFindPetsByStatusKeyFn({ |
218 | | - status |
219 | | - })], |
220 | | - }); |
221 | | - }, |
222 | | - }); |
223 | | - |
224 | | - return ( |
225 | | - <div className="App"> |
226 | | - <h1>Pet List</h1> |
227 | | - <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul> |
228 | | - <button |
229 | | - onClick={() => { |
230 | | - mutate({ name: "Fluffy", status: "available" }); |
231 | | - }} |
232 | | - > |
233 | | - Add Pet |
234 | | - </button> |
235 | | - </div> |
236 | | - ); |
237 | | -} |
238 | | - |
239 | | -export default App; |
240 | | -``` |
241 | | - |
242 | | -##### Using Infinite Query hooks |
243 | | - |
244 | | -This feature will generate a function in infiniteQueries.ts when the name specified by the `pageParam` option exists in the query parameters and the name specified by the `nextPageParam` option exists in the response. |
245 | | - |
246 | | -The `initialPageParam` option can be specified to set the intial page to load, defaults to 1. The `nextPageParam` supports dot notation for nested values (i.e. `meta.next`). |
247 | | - |
248 | | -Example Schema: |
249 | | - |
250 | | -```yml |
251 | | -paths: |
252 | | - /paginated-pets: |
253 | | - get: |
254 | | - description: | |
255 | | - Returns paginated pets from the system that the user has access to |
256 | | - operationId: findPaginatedPets |
257 | | - parameters: |
258 | | - - name: page |
259 | | - in: query |
260 | | - description: page number |
261 | | - required: false |
262 | | - schema: |
263 | | - type: integer |
264 | | - format: int32 |
265 | | - - name: tags |
266 | | - in: query |
267 | | - description: tags to filter by |
268 | | - required: false |
269 | | - style: form |
270 | | - schema: |
271 | | - type: array |
272 | | - items: |
273 | | - type: string |
274 | | - - name: limit |
275 | | - in: query |
276 | | - description: maximum number of results to return |
277 | | - required: false |
278 | | - schema: |
279 | | - type: integer |
280 | | - format: int32 |
281 | | - responses: |
282 | | - '200': |
283 | | - description: pet response |
284 | | - content: |
285 | | - application/json: |
286 | | - schema: |
287 | | - type: object |
288 | | - properties: |
289 | | - pets: |
290 | | - type: array |
291 | | - items: |
292 | | - $ref: '#/components/schemas/Pet' |
293 | | - nextPage: |
294 | | - type: integer |
295 | | - format: int32 |
296 | | - minimum: 1 |
297 | | -``` |
298 | | -
|
299 | | -Usage of Generated Hooks: |
300 | | -
|
301 | | -```ts |
302 | | -import { useFindPaginatedPetsInfinite } from "@/openapi/queries/infiniteQueries"; |
303 | | - |
304 | | -const { data, fetchNextPage } = useFindPaginatedPetsInfinite({ |
305 | | - query: { tags: [], limit: 10 } |
306 | | -}); |
307 | | -``` |
308 | | - |
309 | | -## Development |
310 | | - |
311 | | -### Install dependencies |
312 | | - |
313 | | -```bash |
314 | | -pnpm install |
315 | | -``` |
316 | | - |
317 | | -### Run tests |
318 | | -```bash |
319 | | -pnpm test |
320 | | -``` |
321 | | - |
322 | | -### Run linter |
323 | | -```bash |
324 | | -pnpm lint |
325 | | -``` |
326 | | - |
327 | | -### Run linter and fix |
328 | | -```bash |
329 | | -pnpm lint:fix |
330 | | -``` |
331 | | - |
332 | | -### Update snapshots |
333 | | -```bash |
334 | | -pnpm snapshot |
335 | | -``` |
336 | | - |
337 | | -### Build example and validate generated code |
338 | | - |
339 | | -```bash |
340 | | -npm run build && pnpm --filter @7nohe/react-app generate:api && pnpm --filter @7nohe/react-app test:generated |
341 | | -``` |
342 | | - |
343 | | -## License |
344 | | - |
345 | | -MIT |
0 commit comments