|
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