@aws/nx-plugin-mcp 0.119.0 → 0.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/bin/aws-nx-mcp.js +23 -35
  2. package/docs/guides/astro-docs.mdx +142 -0
  3. package/docs/guides/connection/py-agent-a2a.mdx +105 -0
  4. package/docs/guides/connection/py-agent-mcp.mdx +147 -0
  5. package/docs/guides/connection/react-agui.mdx +240 -0
  6. package/docs/guides/connection/react-fastapi.mdx +1476 -0
  7. package/docs/guides/connection/react-py-agent.mdx +198 -0
  8. package/docs/guides/connection/react-smithy.mdx +1355 -0
  9. package/docs/guides/connection/react-trpc.mdx +447 -0
  10. package/docs/guides/connection/react-ts-agent.mdx +198 -0
  11. package/docs/guides/connection/smithy-rdb.mdx +161 -0
  12. package/docs/guides/connection/trpc-rdb.mdx +127 -0
  13. package/docs/guides/connection/ts-agent-a2a.mdx +106 -0
  14. package/docs/guides/connection/ts-agent-mcp.mdx +144 -0
  15. package/docs/guides/connection/ts-agent-rdb.mdx +141 -0
  16. package/docs/guides/connection/ts-mcp-server-rdb.mdx +135 -0
  17. package/docs/guides/connection.mdx +126 -0
  18. package/docs/guides/docker-bundling.mdx +403 -0
  19. package/docs/guides/fastapi.mdx +509 -0
  20. package/docs/guides/license.mdx +293 -0
  21. package/docs/guides/nx-generator.mdx +588 -0
  22. package/docs/guides/py-agent.mdx +483 -0
  23. package/docs/guides/py-mcp-server.mdx +161 -0
  24. package/docs/guides/python-lambda-function.mdx +207 -0
  25. package/docs/guides/python-project.mdx +228 -0
  26. package/docs/guides/react-website-auth.mdx +216 -0
  27. package/docs/guides/react-website.mdx +421 -0
  28. package/docs/guides/runtime-config.mdx +312 -0
  29. package/docs/guides/terraform-project.mdx +317 -0
  30. package/docs/guides/trpc.mdx +816 -0
  31. package/docs/guides/ts-agent.mdx +436 -0
  32. package/docs/guides/ts-lambda-function.mdx +218 -0
  33. package/docs/guides/ts-mcp-server.mdx +169 -0
  34. package/docs/guides/ts-nx-plugin.mdx +159 -0
  35. package/docs/guides/ts-rdb.mdx +759 -0
  36. package/docs/guides/ts-smithy-api.mdx +661 -0
  37. package/docs/guides/typescript-infrastructure.mdx +408 -0
  38. package/docs/guides/typescript-project.mdx +312 -0
  39. package/docs/guides/workspace.mdx +181 -0
  40. package/docs/snippets/agent/architecture.mdx +72 -0
  41. package/docs/snippets/agent/bedrock-deployment.mdx +172 -0
  42. package/docs/snippets/agent/runtime-arn.mdx +64 -0
  43. package/docs/snippets/api/api-architecture.mdx +93 -0
  44. package/docs/snippets/api/api-choice-note.mdx +6 -0
  45. package/docs/snippets/api/cors-configuration-cdk-note.mdx +25 -0
  46. package/docs/snippets/api/cors-configuration-terraform-note.mdx +28 -0
  47. package/docs/snippets/api/shared-constructs.mdx +38 -0
  48. package/docs/snippets/api/type-safe-api-integrations.mdx +643 -0
  49. package/docs/snippets/api/waf-configuration.mdx +37 -0
  50. package/docs/snippets/connection/a2a-infrastructure.mdx +63 -0
  51. package/docs/snippets/connection/lambda-rdb-ssl-requirements.mdx +40 -0
  52. package/docs/snippets/connection/mcp-server-rdb-ssl-requirements.mdx +35 -0
  53. package/docs/snippets/connection/rdb-api-infrastructure.mdx +72 -0
  54. package/docs/snippets/connection/react-agent-infrastructure.mdx +61 -0
  55. package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +35 -0
  56. package/docs/snippets/lambda-function/architecture.mdx +36 -0
  57. package/docs/snippets/lambda-function/deploying-your-function.mdx +118 -0
  58. package/docs/snippets/mcp/architecture.mdx +58 -0
  59. package/docs/snippets/mcp/assistant-docs.mdx +10 -0
  60. package/docs/snippets/mcp/bedrock-deployment.mdx +167 -0
  61. package/docs/snippets/mcp/config.mdx +13 -0
  62. package/docs/snippets/mcp/configuration-py.mdx +42 -0
  63. package/docs/snippets/mcp/configuration-ts.mdx +53 -0
  64. package/docs/snippets/mcp/observability.mdx +8 -0
  65. package/docs/snippets/mcp/shared-constructs.mdx +32 -0
  66. package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +602 -0
  67. package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +915 -0
  68. package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +161 -0
  69. package/docs/snippets/pdk-migration/example/04-deploy.mdx +229 -0
  70. package/docs/snippets/pdk-migration/faq/aws-arch.mdx +17 -0
  71. package/docs/snippets/pdk-migration/faq/cdk-graph.mdx +29 -0
  72. package/docs/snippets/pdk-migration/faq/infrastructure-python-java.mdx +19 -0
  73. package/docs/snippets/pdk-migration/faq/pdk-nag.mdx +15 -0
  74. package/docs/snippets/pdk-migration/faq/pipeline.mdx +15 -0
  75. package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +310 -0
  76. package/docs/snippets/pdk-migration/faq/use-of-projen.mdx +15 -0
  77. package/docs/snippets/prerequisites.mdx +20 -0
  78. package/docs/snippets/required-prerequisites.mdx +12 -0
  79. package/docs/snippets/shared-constructs.mdx +40 -0
  80. package/docs/snippets/tools/acurl.mdx +73 -0
  81. package/docs/snippets/ts-bundle.mdx +14 -0
  82. package/package.json +1 -1
  83. package/src/py/agent/schema.json +1 -1
  84. package/src/py/fast-api/schema.json +2 -2
  85. package/src/py/mcp-server/schema.json +1 -1
  86. package/src/smithy/ts/api/schema.json +2 -2
  87. package/src/trpc/backend/schema.json +2 -2
  88. package/src/ts/agent/schema.json +1 -1
  89. package/src/ts/mcp-server/schema.json +1 -1
@@ -0,0 +1,1355 @@
1
+ ---
2
+ title: React to Smithy API
3
+ description: Connect a React website to a Smithy TypeScript API
4
+ when:
5
+ sourceType: react
6
+ targetType: smithy
7
+ ---
8
+
9
+ import { FileTree, Steps } from '@astrojs/starlight/components';
10
+ import Link from '@components/link.astro';
11
+ import RunGenerator from '@components/run-generator.astro';
12
+ import NxCommands from '@components/nx-commands.astro';
13
+ import PackageManagerShortCommand from '@components/package-manager-short-command.astro';
14
+ import Drawer from '@components/drawer.astro';
15
+ import GeneratorParameters from '@components/generator-parameters.astro';
16
+
17
+ The `connection` generator provides a way to quickly integrate your React website with your Smithy TypeScript API backend. It sets up all necessary configuration for connecting to your Smithy API in a type-safe manner, including client and [TanStack Query](https://tanstack.com/query/v5) hooks generation, AWS IAM and Cognito authentication support and proper error handling.
18
+
19
+ ## Prerequisites
20
+
21
+ Before using this generator, ensure your React application has:
22
+
23
+ 1. A `main.tsx` file that renders your application
24
+ 2. A working Smithy TypeScript API backend (generated using the <Link path="/guides/ts-smithy-api">`ts#smithy-api` generator</Link>)
25
+ 3. Cognito Auth added via the <Link path="/guides/react-website-auth">`ts#react-website-auth` generator</Link> if connecting an API which uses Cognito or IAM auth
26
+
27
+ <details>
28
+ <summary>Example of required `main.tsx` structure</summary>
29
+
30
+ ```tsx
31
+ import { StrictMode } from 'react';
32
+ import * as ReactDOM from 'react-dom/client';
33
+ import App from './app/app';
34
+
35
+ const root = ReactDOM.createRoot(
36
+ document.getElementById('root') as HTMLElement,
37
+ );
38
+ root.render(
39
+ <StrictMode>
40
+ <App />
41
+ </StrictMode>,
42
+ );
43
+ ```
44
+
45
+ </details>
46
+
47
+ ## Usage
48
+
49
+ ### Run the Generator
50
+
51
+ <RunGenerator generator="connection" />
52
+
53
+ ### Options
54
+
55
+ <GeneratorParameters generator="connection" />
56
+
57
+ ## Generator Output
58
+
59
+ The generator will make changes to the following files in your React application:
60
+
61
+ <FileTree>
62
+
63
+ - src
64
+ - components
65
+ - \<ApiName>Provider.tsx Provider for your API client
66
+ - QueryClientProvider.tsx TanStack React Query client provider
67
+ - RuntimeConfig/ Runtime configuration component for local development
68
+ - hooks
69
+ - use\<ApiName>.tsx Add a hook for calling your API with state managed by TanStack Query
70
+ - use\<ApiName>Client.tsx Add a hook for instantiating the vanilla API client which can call your API.
71
+ - useSigV4.tsx Add a hook for signing HTTP requests with SigV4 (if you selected IAM authentication)
72
+ - project.json A new target is added to the build which generates a type-safe client
73
+ - .gitignore The generated client files are ignored by default
74
+
75
+ </FileTree>
76
+
77
+ The generator will also add a file to your Smithy model:
78
+
79
+ <FileTree>
80
+
81
+ - model
82
+ - src
83
+ - extensions.smithy Defines traits which can be used to customise the generated client
84
+
85
+ </FileTree>
86
+
87
+ The generator will also add Runtime Config to your website infrastructure if not present already, which ensures that the API URL for your Smithy API is available in the website and automatically configured by the `use<ApiName>.tsx` hook.
88
+
89
+ ### Code Generation
90
+
91
+ At build time, a type-safe client is generated from your Smithy API's OpenAPI specification. This will add three new files to your React application:
92
+
93
+ <FileTree>
94
+
95
+ - src
96
+ - generated
97
+ - \<ApiName>
98
+ - types.gen.ts Generated types from the Smithy model structures
99
+ - client.gen.ts Type-safe client for calling your API
100
+ - options-proxy.gen.ts Provides methods to create TanStack Query hooks options for interacting with your API using TanStack Query
101
+
102
+ </FileTree>
103
+
104
+ :::tip[Generated Client in Git]
105
+ By default, the generated client is ignored from version control. If you would prefer to check it in, you can remove the entry from your React application's `.gitignore` file, however note that any manual changes to the `.gen.ts` files will be overwritten when your project is built.
106
+ :::
107
+
108
+ ## Using the Generated Code
109
+
110
+ The generated type-safe client can be used to call your Smithy API from your React application. It's recommended to make use of the client via the TanStack Query hooks, but you can use the vanilla client if you prefer.
111
+
112
+ :::note[Regenerating Client Code]
113
+ Whenever you make changes to your Smithy API model, you need to rebuild your project in order for those changes to be reflected in the generated client. For example:
114
+
115
+ <PackageManagerShortCommand commands={["build"]} />
116
+ :::
117
+
118
+ :::tip[Auto-Regeneration]
119
+ If you're actively working on both your React application and Smithy API together, use the React application's `serve-local` target which will automatically regenerate the client whenever your API changes, as well as hot-reloading your website and local Smithy API server:
120
+
121
+ <NxCommands commands={['serve-local <WebsiteProject>']} />
122
+
123
+ For more fine-grained control, you can use the `watch-generate:<ApiName>-client` target for your React application to regenerate the client every time you make API changes:
124
+
125
+ <NxCommands commands={['run <WebsiteProject>:"watch-generate:<ApiName>-client"']}
126
+ />
127
+ :::
128
+
129
+ ### Using the API Hook
130
+
131
+ The generator provides a `use<ApiName>` hook which you can use to call your API with TanStack Query.
132
+
133
+ ### Queries
134
+
135
+ You can use the `queryOptions` method to retrieve the options required for calling your API using TanStack Query's `useQuery` hook:
136
+
137
+ ```tsx {7}
138
+ import { useQuery } from '@tanstack/react-query';
139
+ import { useState, useEffect } from 'react';
140
+ import { useMyApi } from './hooks/useMyApi';
141
+
142
+ function MyComponent() {
143
+ const api = useMyApi();
144
+ const item = useQuery(api.getItem.queryOptions({ itemId: 'some-id' }));
145
+
146
+ if (item.isLoading) return <div>Loading...</div>;
147
+ if (item.isError) return <div>Error: {item.error.message}</div>;
148
+
149
+ return <div>Item: {item.data.name}</div>;
150
+ }
151
+ ```
152
+
153
+ <Drawer title="Using the API client directly" trigger="Click here for an example using the vanilla client directly.">
154
+ ```tsx {5,13}
155
+ import { useState, useEffect } from 'react';
156
+ import { useMyApiClient } from './hooks/useMyApiClient';
157
+
158
+ function MyComponent() {
159
+ const api = useMyApiClient();
160
+ const [item, setItem] = useState(null);
161
+ const [loading, setLoading] = useState(true);
162
+ const [error, setError] = useState(null);
163
+
164
+ useEffect(() => {
165
+ const fetchItem = async () => {
166
+ try {
167
+ const data = await api.getItem({ itemId: 'some-id' });
168
+ setItem(data);
169
+ } catch (err) {
170
+ setError(err);
171
+ } finally {
172
+ setLoading(false);
173
+ }
174
+ };
175
+ fetchItem();
176
+ }, [api]);
177
+
178
+ if (loading) return <div>Loading...</div>;
179
+ if (error) return <div>Error: {error.message}</div>;
180
+
181
+ return <div>Item: {item.name}</div>;
182
+ }
183
+ ```
184
+ </Drawer>
185
+
186
+ ### Mutations
187
+
188
+ The generated hooks include support for mutations using TanStack Query's `useMutation` hook. This provides a clean way to handle create, update, and delete operations with loading states, error handling, and optimistic updates.
189
+
190
+ ```tsx {5-7,11}
191
+ import { useMutation } from '@tanstack/react-query';
192
+ import { useMyApi } from './hooks/useMyApi';
193
+
194
+ function CreateItemForm() {
195
+ const api = useMyApi();
196
+ // Create a mutation using the generated mutation options
197
+ const createItem = useMutation(api.createItem.mutationOptions());
198
+
199
+ const handleSubmit = (e) => {
200
+ e.preventDefault();
201
+ createItem.mutate({ name: 'New Item', description: 'A new item' });
202
+ };
203
+
204
+ return (
205
+ <form onSubmit={handleSubmit}>
206
+ {/* Form fields */}
207
+ <button
208
+ type="submit"
209
+ disabled={createItem.isPending}
210
+ >
211
+ {createItem.isPending ? 'Creating...' : 'Create Item'}
212
+ </button>
213
+
214
+ {createItem.isSuccess && (
215
+ <div className="success">
216
+ Item created with ID: {createItem.data.id}
217
+ </div>
218
+ )}
219
+
220
+ {createItem.isError && (
221
+ <div className="error">
222
+ Error: {createItem.error.message}
223
+ </div>
224
+ )}
225
+ </form>
226
+ );
227
+ }
228
+ ```
229
+
230
+ You can also add callbacks for different mutation states:
231
+
232
+ ```tsx
233
+ const createItem = useMutation({
234
+ ...api.createItem.mutationOptions(),
235
+ onSuccess: (data) => {
236
+ // This will run when the mutation succeeds
237
+ console.log('Item created:', data);
238
+ // You can navigate to the new item
239
+ navigate(`/items/${data.id}`);
240
+ },
241
+ onError: (error) => {
242
+ // This will run when the mutation fails
243
+ console.error('Failed to create item:', error);
244
+ },
245
+ onSettled: () => {
246
+ // This will run when the mutation completes (success or error)
247
+ // Good place to invalidate queries that might be affected
248
+ queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() });
249
+ }
250
+ });
251
+ ```
252
+
253
+ <Drawer title="Mutations using the API client directly" trigger="Click here for an example using the client directly.">
254
+ ```tsx
255
+ import { useState } from 'react';
256
+ import { useMyApiClient } from './hooks/useMyApiClient';
257
+
258
+ function CreateItemForm() {
259
+ const api = useMyApiClient();
260
+ const [isLoading, setIsLoading] = useState(false);
261
+ const [error, setError] = useState(null);
262
+ const [createdItem, setCreatedItem] = useState(null);
263
+
264
+ const handleSubmit = async (e) => {
265
+ e.preventDefault();
266
+ setIsLoading(true);
267
+ setError(null);
268
+
269
+ try {
270
+ const newItem = await api.createItem({
271
+ name: 'New Item',
272
+ description: 'A new item'
273
+ });
274
+ setCreatedItem(newItem);
275
+ // You can navigate to the new item
276
+ // navigate(`/items/${newItem.id}`);
277
+ } catch (err) {
278
+ setError(err);
279
+ console.error('Failed to create item:', err);
280
+ } finally {
281
+ setIsLoading(false);
282
+ }
283
+ };
284
+
285
+ return (
286
+ <form onSubmit={handleSubmit}>
287
+ {/* Form fields */}
288
+ <button
289
+ type="submit"
290
+ disabled={isLoading}
291
+ >
292
+ {isLoading ? 'Creating...' : 'Create Item'}
293
+ </button>
294
+
295
+ {createdItem && (
296
+ <div className="success">
297
+ Item created with ID: {createdItem.id}
298
+ </div>
299
+ )}
300
+
301
+ {error && (
302
+ <div className="error">
303
+ Error: {error.message}
304
+ </div>
305
+ )}
306
+ </form>
307
+ );
308
+ }
309
+ ```
310
+ </Drawer>
311
+
312
+ ### Pagination with Infinite Queries
313
+
314
+ For endpoints that accept a `cursor` parameter as input, the generated hooks provide support for infinite queries using TanStack Query's `useInfiniteQuery` hook. This makes it easy to implement "load more" or infinite scrolling functionality.
315
+
316
+ ```tsx {6-16,30-34}
317
+ import { useInfiniteQuery } from '@tanstack/react-query';
318
+ import { useMyApi } from './hooks/useMyApi';
319
+
320
+ function ItemList() {
321
+ const api = useMyApi();
322
+ const items = useInfiniteQuery({
323
+ ...api.listItems.infiniteQueryOptions({
324
+ limit: 10, // Number of items per page
325
+ }, {
326
+ // Make sure you define a getNextPageParam function to return
327
+ // the parameter that should be passed as the 'cursor' for the
328
+ // next page
329
+ getNextPageParam: (lastPage) =>
330
+ lastPage.nextCursor || undefined
331
+ }),
332
+ });
333
+
334
+ if (items.isLoading) {
335
+ return <LoadingSpinner />;
336
+ }
337
+
338
+ if (items.isError) {
339
+ return <ErrorMessage message={items.error.message} />;
340
+ }
341
+
342
+ return (
343
+ <div>
344
+ {/* Flatten the pages array to render all items */}
345
+ <ul>
346
+ {items.data.pages.flatMap(page =>
347
+ page.items.map(item => (
348
+ <li key={item.id}>{item.name}</li>
349
+ ))
350
+ )}
351
+ </ul>
352
+
353
+ <button
354
+ onClick={() => items.fetchNextPage()}
355
+ disabled={!items.hasNextPage || items.isFetchingNextPage}
356
+ >
357
+ {items.isFetchingNextPage
358
+ ? 'Loading more...'
359
+ : items.hasNextPage
360
+ ? 'Load More'
361
+ : 'No more items'}
362
+ </button>
363
+ </div>
364
+ );
365
+ }
366
+ ```
367
+
368
+ The generated hooks automatically handle cursor-based pagination if your API supports it. The `nextCursor` value is extracted from the response and used to fetch the next page.
369
+
370
+ :::tip[Paginated APIs]
371
+ If you have a paginated API which has its pagination parameter named something other than `cursor`, you can [customise it using the `x-cursor` OpenAPI vendor extension](#custom-pagination-cursor).
372
+ :::
373
+
374
+ <Drawer title="Pagination using the API client directly" trigger="Click here for an example using the client directly.">
375
+ ```tsx
376
+ import { useState, useEffect } from 'react';
377
+ import { useMyApiClient } from './hooks/useMyApiClient';
378
+
379
+ function ItemList() {
380
+ const api = useMyApiClient();
381
+ const [items, setItems] = useState([]);
382
+ const [isLoading, setIsLoading] = useState(true);
383
+ const [error, setError] = useState(null);
384
+ const [nextCursor, setNextCursor] = useState(null);
385
+ const [isFetchingMore, setIsFetchingMore] = useState(false);
386
+
387
+ // Fetch initial data
388
+ useEffect(() => {
389
+ const fetchItems = async () => {
390
+ try {
391
+ setIsLoading(true);
392
+ const response = await api.listItems({ limit: 10 });
393
+ setItems(response.items);
394
+ setNextCursor(response.nextCursor);
395
+ } catch (err) {
396
+ setError(err);
397
+ } finally {
398
+ setIsLoading(false);
399
+ }
400
+ };
401
+
402
+ fetchItems();
403
+ }, [api]);
404
+
405
+ // Function to load more items
406
+ const loadMore = async () => {
407
+ if (!nextCursor) return;
408
+
409
+ try {
410
+ setIsFetchingMore(true);
411
+ const response = await api.listItems({
412
+ limit: 10,
413
+ cursor: nextCursor
414
+ });
415
+
416
+ setItems(prevItems => [...prevItems, ...response.items]);
417
+ setNextCursor(response.nextCursor);
418
+ } catch (err) {
419
+ setError(err);
420
+ } finally {
421
+ setIsFetchingMore(false);
422
+ }
423
+ };
424
+
425
+ if (isLoading) {
426
+ return <LoadingSpinner />;
427
+ }
428
+
429
+ if (error) {
430
+ return <ErrorMessage message={error.message} />;
431
+ }
432
+
433
+ return (
434
+ <div>
435
+ <ul>
436
+ {items.map(item => (
437
+ <li key={item.id}>{item.name}</li>
438
+ ))}
439
+ </ul>
440
+
441
+ <button
442
+ onClick={loadMore}
443
+ disabled={!nextCursor || isFetchingMore}
444
+ >
445
+ {isFetchingMore
446
+ ? 'Loading more...'
447
+ : nextCursor
448
+ ? 'Load More'
449
+ : 'No more items'}
450
+ </button>
451
+ </div>
452
+ );
453
+ }
454
+ ```
455
+ </Drawer>
456
+
457
+ ### Error Handling
458
+
459
+ The integration includes built-in error handling with typed error responses. An `<operation-name>Error` type is generated which encapsulates the possible error responses defined in the Smithy model. Each error has a `status` and `error` property, and by checking the value of `status` you can narrow to a specific type of error.
460
+
461
+ ```tsx {12}
462
+ import { useMutation } from '@tanstack/react-query';
463
+
464
+ function MyComponent() {
465
+ const api = useMyApi();
466
+ const createItem = useMutation(api.createItem.mutationOptions());
467
+
468
+ const handleClick = () => {
469
+ createItem.mutate({ name: 'New Item' });
470
+ };
471
+
472
+ if (createItem.error) {
473
+ switch (createItem.error.status) {
474
+ case 400:
475
+ // error.error is typed as CreateItem400Response
476
+ return (
477
+ <div>
478
+ <h2>Invalid input:</h2>
479
+ <p>{createItem.error.error.message}</p>
480
+ </div>
481
+ );
482
+ case 403:
483
+ // error.error is typed as CreateItem403Response
484
+ return (
485
+ <div>
486
+ <h2>Not authorized:</h2>
487
+ <p>{createItem.error.error.reason}</p>
488
+ </div>
489
+ );
490
+ case 500:
491
+ case 502:
492
+ // error.error is typed as CreateItem5XXResponse
493
+ return (
494
+ <div>
495
+ <h2>Server error:</h2>
496
+ <p>{createItem.error.error.message}</p>
497
+ </div>
498
+ );
499
+ }
500
+ }
501
+
502
+ return <button onClick={handleClick}>Create Item</button>;
503
+ }
504
+ ```
505
+
506
+ <Drawer title="Error handling using the API client directly" trigger="Click here for an example using the vanilla client directly.">
507
+ ```tsx {9,15}
508
+ function MyComponent() {
509
+ const api = useMyApiClient();
510
+ const [error, setError] = useState<CreateItemError | null>(null);
511
+
512
+ const handleClick = async () => {
513
+ try {
514
+ await api.createItem({ name: 'New Item' });
515
+ } catch (e) {
516
+ const err = e as CreateItemError;
517
+ setError(err);
518
+ }
519
+ };
520
+
521
+ if (error) {
522
+ switch (error.status) {
523
+ case 400:
524
+ // error.error is typed as CreateItem400Response
525
+ return (
526
+ <div>
527
+ <h2>Invalid input:</h2>
528
+ <p>{error.error.message}</p>
529
+ </div>
530
+ );
531
+ case 403:
532
+ // error.error is typed as CreateItem403Response
533
+ return (
534
+ <div>
535
+ <h2>Not authorized:</h2>
536
+ <p>{error.error.reason}</p>
537
+ </div>
538
+ );
539
+ case 500:
540
+ case 502:
541
+ // error.error is typed as CreateItem5XXResponse
542
+ return (
543
+ <div>
544
+ <h2>Server error:</h2>
545
+ <p>{error.error.message}</p>
546
+ </div>
547
+ );
548
+ }
549
+ }
550
+
551
+ return <button onClick={handleClick}>Create Item</button>;
552
+ }
553
+ ```
554
+ </Drawer>
555
+
556
+ ## Customising the Generated Code
557
+
558
+ A selection of Smithy traits are added to your target Smithy `model` project in `extensions.smithy` which you can use to customise the generated client.
559
+
560
+ :::tip[Default Operations]
561
+ If you do not need to customise the generated client, you can safely delete `extensions.smithy`
562
+ :::
563
+
564
+ ### Queries and Mutations
565
+
566
+ By default, operations in your Smithy API which use the HTTP methods `PUT`, `POST`, `PATCH` and `DELETE` are considered mutations, and all others are considered queries.
567
+
568
+ You can change this behaviour using the `@query` and `@mutation` Smithy traits which are added to your model project in `extensions.smithy`.
569
+
570
+ :::note[OpenAPI Extensions]
571
+ These map to OpenAPI vendor extensions using the [`@specificationExtension` trait](https://smithy.io/2.0/guides/model-translations/converting-to-openapi.html#specificationextension-trait), which our code generator interprets when generating the client from the OpenAPI specification.
572
+ :::
573
+
574
+ #### @query
575
+
576
+ Then Apply the `@query` trait to your Smithy operation to force it to be treated as a query:
577
+
578
+ ```smithy
579
+ @http(method: "POST", uri: "/items")
580
+ @query
581
+ operation ListItems {
582
+ input: ListItemsInput
583
+ output: ListItemsOutput
584
+ }
585
+ ```
586
+
587
+ The generated hook will provide `queryOptions` even though it uses the `POST` HTTP method:
588
+
589
+ ```tsx
590
+ const items = useQuery(api.listItems.queryOptions());
591
+ ```
592
+
593
+ #### @mutation
594
+
595
+ Apply the `@mutation` trait to your Smithy operation to force it to be treated as a mutation:
596
+
597
+ ```smithy
598
+ @http(method: "GET", uri: "/start-processing")
599
+ @mutation
600
+ operation StartProcessing {
601
+ input: StartProcessingInput
602
+ output: StartProcessingOutput
603
+ }
604
+ ```
605
+
606
+ The generated hook will provide `mutationOptions` even though it uses the `GET` HTTP method:
607
+
608
+ ```tsx
609
+ const startProcessing = useMutation(api.startProcessing.mutationOptions());
610
+ ```
611
+
612
+ ### Custom Pagination Cursor
613
+
614
+ By default, the generated hooks assume cursor-based pagination with a parameter named `cursor`. You can customize this behavior using the `@cursor` trait which is added to your model project in `extensions.smithy`.
615
+
616
+ Apply the `@cursor` trait with `inputToken` to change the name of the input parameter used for the pagination token:
617
+
618
+ ```smithy
619
+ @http(method: "GET", uri: "/items")
620
+ @cursor(inputToken: "nextToken")
621
+ operation ListItems {
622
+ input := {
623
+ nextToken: String
624
+ limit: Integer
625
+ }
626
+ output := {
627
+ items: ItemList
628
+ nextToken: String
629
+ }
630
+ }
631
+ ```
632
+
633
+ If you would not like to generate `infiniteQueryOptions` for an operation which has an input parameter named `cursor`, you can disable cursor-based pagination:
634
+
635
+ ```smithy
636
+ @cursor(enabled: false)
637
+ operation ListItems {
638
+ input := {
639
+ // Input parameter named 'cursor' will cause this operation to be treated as a paginated operation by default
640
+ cursor: String
641
+ }
642
+ output := {
643
+ ...
644
+ }
645
+ }
646
+ ```
647
+
648
+ ### Grouping Operations
649
+
650
+ The generated hooks and client methods are automatically organized based on the [`@tags` trait](https://smithy.io/2.0/spec/documentation-traits.html#tags-trait) in your Smithy operations. Operations with the same tags are grouped together, which helps keep your API calls organized and provides better code completion in your IDE.
651
+
652
+ For example, with this Smithy model:
653
+
654
+ ```smithy
655
+ service MyService {
656
+ operations: [ListItems, CreateItem, ListUsers, CreateUser]
657
+ }
658
+
659
+ @tags(["items"])
660
+ operation ListItems {
661
+ input: ListItemsInput
662
+ output: ListItemsOutput
663
+ }
664
+
665
+ @tags(["items"])
666
+ operation CreateItem {
667
+ input: CreateItemInput
668
+ output: CreateItemOutput
669
+ }
670
+
671
+ @tags(["users"])
672
+ operation ListUsers {
673
+ input: ListUsersInput
674
+ output: ListUsersOutput
675
+ }
676
+
677
+ @tags(["users"])
678
+ operation CreateUser {
679
+ input: CreateUserInput
680
+ output: CreateUserOutput
681
+ }
682
+ ```
683
+
684
+ The generated hooks will be grouped by tags:
685
+
686
+ ```tsx
687
+ import { useQuery, useMutation } from '@tanstack/react-query';
688
+ import { useMyApi } from './hooks/useMyApi';
689
+
690
+ function ItemsAndUsers() {
691
+ const api = useMyApi();
692
+
693
+ // Items operations are grouped under api.items
694
+ const items = useQuery(api.items.listItems.queryOptions());
695
+ const createItem = useMutation(api.items.createItem.mutationOptions());
696
+
697
+ // Users operations are grouped under api.users
698
+ const users = useQuery(api.users.listUsers.queryOptions());
699
+
700
+ // Usage example
701
+ const handleCreateItem = () => {
702
+ createItem.mutate({ name: 'New Item' });
703
+ };
704
+
705
+ return (
706
+ <div>
707
+ <h2>Items</h2>
708
+ <ul>
709
+ {items.data?.map(item => (
710
+ <li key={item.id}>{item.name}</li>
711
+ ))}
712
+ </ul>
713
+ <button onClick={handleCreateItem}>Add Item</button>
714
+
715
+ <h2>Users</h2>
716
+ <ul>
717
+ {users.data?.map(user => (
718
+ <li key={user.id}>{user.name}</li>
719
+ ))}
720
+ </ul>
721
+ </div>
722
+ );
723
+ }
724
+ ```
725
+
726
+ This grouping makes it easier to organize your API calls and provides better code completion in your IDE.
727
+
728
+ <Drawer title="Grouped operations using the API client directly" trigger="Click here for an example using the client directly.">
729
+ ```tsx
730
+ import { useState, useEffect } from 'react';
731
+ import { useMyApiClient } from './hooks/useMyApiClient';
732
+
733
+ function ItemsAndUsers() {
734
+ const api = useMyApiClient();
735
+ const [items, setItems] = useState([]);
736
+ const [users, setUsers] = useState([]);
737
+ const [isLoading, setIsLoading] = useState(true);
738
+
739
+ // Load data
740
+ useEffect(() => {
741
+ const fetchData = async () => {
742
+ try {
743
+ setIsLoading(true);
744
+
745
+ // Items operations are grouped under api.items
746
+ const itemsData = await api.items.listItems();
747
+ setItems(itemsData);
748
+
749
+ // Users operations are grouped under api.users
750
+ const usersData = await api.users.listUsers();
751
+ setUsers(usersData);
752
+ } catch (error) {
753
+ console.error('Error fetching data:', error);
754
+ } finally {
755
+ setIsLoading(false);
756
+ }
757
+ };
758
+
759
+ fetchData();
760
+ }, [api]);
761
+
762
+ const handleCreateItem = async () => {
763
+ try {
764
+ // Create item using the grouped method
765
+ const newItem = await api.items.createItem({ name: 'New Item' });
766
+ setItems(prevItems => [...prevItems, newItem]);
767
+ } catch (error) {
768
+ console.error('Error creating item:', error);
769
+ }
770
+ };
771
+
772
+ if (isLoading) {
773
+ return <div>Loading...</div>;
774
+ }
775
+
776
+ return (
777
+ <div>
778
+ <h2>Items</h2>
779
+ <ul>
780
+ {items.map(item => (
781
+ <li key={item.id}>{item.name}</li>
782
+ ))}
783
+ </ul>
784
+ <button onClick={handleCreateItem}>Add Item</button>
785
+
786
+ <h2>Users</h2>
787
+ <ul>
788
+ {users.map(user => (
789
+ <li key={user.id}>{user.name}</li>
790
+ ))}
791
+ </ul>
792
+ </div>
793
+ );
794
+ }
795
+ ```
796
+ </Drawer>
797
+
798
+ ### Errors
799
+
800
+ You can customize error responses in your Smithy API by defining custom error structures in your Smithy model. The generated client will automatically handle these custom error types.
801
+
802
+ #### Defining Custom Error Structures
803
+
804
+ Define your error structures in your Smithy model:
805
+
806
+ ```smithy
807
+ @error("client")
808
+ @httpError(400)
809
+ structure InvalidRequestError {
810
+ @required
811
+ message: String
812
+
813
+ fieldErrors: FieldErrorList
814
+ }
815
+
816
+ @error("client")
817
+ @httpError(403)
818
+ structure UnauthorizedError {
819
+ @required
820
+ reason: String
821
+ }
822
+
823
+ @error("server")
824
+ @httpError(500)
825
+ structure InternalServerError {
826
+ @required
827
+ message: String
828
+
829
+ traceId: String
830
+ }
831
+
832
+ list FieldErrorList {
833
+ member: FieldError
834
+ }
835
+
836
+ structure FieldError {
837
+ @required
838
+ field: String
839
+
840
+ @required
841
+ message: String
842
+ }
843
+ ```
844
+
845
+ #### Adding Errors to Operations
846
+
847
+ Specify which errors your operations can return:
848
+
849
+ ```smithy
850
+ operation CreateItem {
851
+ input: CreateItemInput
852
+ output: CreateItemOutput
853
+ errors: [
854
+ InvalidRequestError
855
+ UnauthorizedError
856
+ InternalServerError
857
+ ]
858
+ }
859
+
860
+ operation GetItem {
861
+ input: GetItemInput
862
+ output: GetItemOutput
863
+ errors: [
864
+ ItemNotFoundError
865
+ InternalServerError
866
+ ]
867
+ }
868
+
869
+ @error("client")
870
+ @httpError(404)
871
+ structure ItemNotFoundError {
872
+ @required
873
+ message: String
874
+ }
875
+ ```
876
+
877
+ #### Using Custom Error Types in React
878
+
879
+ The generated client will automatically handle these custom error types, allowing you to type-check and handle different error responses:
880
+
881
+ ```tsx
882
+ import { useMutation, useQuery } from '@tanstack/react-query';
883
+
884
+ function ItemComponent() {
885
+ const api = useMyApi();
886
+
887
+ // Query with typed error handling
888
+ const getItem = useQuery({
889
+ ...api.getItem.queryOptions({ itemId: '123' }),
890
+ onError: (error) => {
891
+ // Error is typed based on the errors in your Smithy model
892
+ switch (error.status) {
893
+ case 404:
894
+ // error.error is typed as ItemNotFoundError
895
+ console.error('Not found:', error.error.message);
896
+ break;
897
+ case 500:
898
+ // error.error is typed as InternalServerError
899
+ console.error('Server error:', error.error.message);
900
+ console.error('Trace ID:', error.error.traceId);
901
+ break;
902
+ }
903
+ }
904
+ });
905
+
906
+ // Mutation with typed error handling
907
+ const createItem = useMutation({
908
+ ...api.createItem.mutationOptions(),
909
+ onError: (error) => {
910
+ switch (error.status) {
911
+ case 400:
912
+ // error.error is typed as InvalidRequestError
913
+ console.error('Validation error:', error.error.message);
914
+ console.error('Field errors:', error.error.fieldErrors);
915
+ break;
916
+ case 403:
917
+ // error.error is typed as UnauthorizedError
918
+ console.error('Unauthorized:', error.error.reason);
919
+ break;
920
+ }
921
+ }
922
+ });
923
+
924
+ // Component rendering with error handling
925
+ if (getItem.isError) {
926
+ if (getItem.error.status === 404) {
927
+ return <NotFoundMessage message={getItem.error.error.message} />;
928
+ } else if (getItem.error.status === 500) {
929
+ return <ErrorMessage message={getItem.error.error.message} />;
930
+ }
931
+ }
932
+
933
+ return (
934
+ <div>
935
+ {/* Component content */}
936
+ </div>
937
+ );
938
+ }
939
+ ```
940
+
941
+ <Drawer title="Handling custom errors with the client directly" trigger="Click here for an example using the client directly.">
942
+ ```tsx
943
+ import { useState, useEffect } from 'react';
944
+
945
+ function ItemComponent() {
946
+ const api = useMyApiClient();
947
+ const [item, setItem] = useState(null);
948
+ const [error, setError] = useState(null);
949
+ const [loading, setLoading] = useState(true);
950
+
951
+ // Fetch item with error handling
952
+ useEffect(() => {
953
+ const fetchItem = async () => {
954
+ try {
955
+ setLoading(true);
956
+ const data = await api.getItem({ itemId: '123' });
957
+ setItem(data);
958
+ } catch (e) {
959
+ // Error is typed based on the errors in your Smithy model
960
+ const err = e as GetItemError;
961
+ setError(err);
962
+
963
+ switch (err.status) {
964
+ case 404:
965
+ // err.error is typed as ItemNotFoundError
966
+ console.error('Not found:', err.error.message);
967
+ break;
968
+ case 500:
969
+ // err.error is typed as InternalServerError
970
+ console.error('Server error:', err.error.message);
971
+ console.error('Trace ID:', err.error.traceId);
972
+ break;
973
+ }
974
+ } finally {
975
+ setLoading(false);
976
+ }
977
+ };
978
+
979
+ fetchItem();
980
+ }, [api]);
981
+
982
+ // Create item with error handling
983
+ const handleCreateItem = async (data) => {
984
+ try {
985
+ await api.createItem(data);
986
+ } catch (e) {
987
+ const err = e as CreateItemError;
988
+
989
+ switch (err.status) {
990
+ case 400:
991
+ // err.error is typed as InvalidRequestError
992
+ console.error('Validation error:', err.error.message);
993
+ console.error('Field errors:', err.error.fieldErrors);
994
+ break;
995
+ case 403:
996
+ // err.error is typed as UnauthorizedError
997
+ console.error('Unauthorized:', err.error.reason);
998
+ break;
999
+ }
1000
+ }
1001
+ };
1002
+
1003
+ // Component rendering with error handling
1004
+ if (loading) {
1005
+ return <LoadingSpinner />;
1006
+ }
1007
+
1008
+ if (error) {
1009
+ if (error.status === 404) {
1010
+ return <NotFoundMessage message={error.error.message} />;
1011
+ } else if (error.status === 500) {
1012
+ return <ErrorMessage message={error.error.message} />;
1013
+ }
1014
+ }
1015
+
1016
+ return (
1017
+ <div>
1018
+ {/* Component content */}
1019
+ </div>
1020
+ );
1021
+ }
1022
+ ```
1023
+ </Drawer>
1024
+
1025
+ :::tip[Error Structures]
1026
+ When defining error structures in Smithy, always use the `@error` and `@httpError` traits to specify the error type and HTTP status code. This ensures that the generated client will have proper type information for error handling.
1027
+ :::
1028
+
1029
+ ## Best Practices
1030
+
1031
+ ### Handle Loading States
1032
+
1033
+ Always handle loading and error states for a better user experience:
1034
+
1035
+ ```tsx
1036
+ import { useQuery } from '@tanstack/react-query';
1037
+
1038
+ function ItemList() {
1039
+ const api = useMyApi();
1040
+ const items = useQuery(api.listItems.queryOptions());
1041
+
1042
+ if (items.isLoading) {
1043
+ return <LoadingSpinner />;
1044
+ }
1045
+
1046
+ if (items.isError) {
1047
+ const err = items.error;
1048
+ switch (err.status) {
1049
+ case 403:
1050
+ // err.error is typed as ListItems403Response
1051
+ return <ErrorMessage message={err.error.reason} />;
1052
+ case 500:
1053
+ case 502:
1054
+ // err.error is typed as ListItems5XXResponse
1055
+ return (
1056
+ <ErrorMessage
1057
+ message={err.error.message}
1058
+ />
1059
+ );
1060
+ default:
1061
+ return <ErrorMessage message="An unknown error occurred" />;
1062
+ }
1063
+ }
1064
+
1065
+ return (
1066
+ <ul>
1067
+ {items.data.map((item) => (
1068
+ <li key={item.id}>{item.name}</li>
1069
+ ))}
1070
+ </ul>
1071
+ );
1072
+ }
1073
+ ```
1074
+
1075
+ <Drawer title="Handle loading states using the API client directly" trigger="Click here for an example using the vanilla client directly.">
1076
+ ```tsx
1077
+ function ItemList() {
1078
+ const api = useMyApiClient();
1079
+ const [items, setItems] = useState([]);
1080
+ const [loading, setLoading] = useState(true);
1081
+ const [error, setError] = useState(null);
1082
+
1083
+ useEffect(() => {
1084
+ const fetchItems = async () => {
1085
+ try {
1086
+ const data = await api.listItems();
1087
+ setItems(data);
1088
+ } catch (err) {
1089
+ setError(err);
1090
+ } finally {
1091
+ setLoading(false);
1092
+ }
1093
+ };
1094
+ fetchItems();
1095
+ }, [api]);
1096
+
1097
+ if (loading) {
1098
+ return <LoadingSpinner />;
1099
+ }
1100
+
1101
+ if (error) {
1102
+ const err = error as ListItemsError;
1103
+ switch (err.status) {
1104
+ case 403:
1105
+ // err.error is typed as ListItems403Response
1106
+ return <ErrorMessage message={err.error.reason} />;
1107
+ case 500:
1108
+ case 502:
1109
+ // err.error is typed as ListItems5XXResponse
1110
+ return (
1111
+ <ErrorMessage
1112
+ message={err.error.message}
1113
+ />
1114
+ );
1115
+ default:
1116
+ return <ErrorMessage message="An unknown error occurred" />;
1117
+ }
1118
+ }
1119
+
1120
+ return (
1121
+ <ul>
1122
+ {items.map((item) => (
1123
+ <li key={item.id}>{item.name}</li>
1124
+ ))}
1125
+ </ul>
1126
+ );
1127
+ }
1128
+ ```
1129
+ </Drawer>
1130
+
1131
+ ### Optimistic Updates
1132
+
1133
+ Implement optimistic updates for a better user experience:
1134
+
1135
+ ```tsx
1136
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
1137
+
1138
+ function ItemList() {
1139
+ const api = useMyApi();
1140
+ const queryClient = useQueryClient();
1141
+
1142
+ // Query to fetch items
1143
+ const itemsQuery = useQuery(api.listItems.queryOptions());
1144
+
1145
+ // Mutation for deleting items with optimistic updates
1146
+ const deleteMutation = useMutation({
1147
+ ...api.deleteItem.mutationOptions(),
1148
+ onMutate: async (itemId) => {
1149
+ // Cancel any outgoing refetches
1150
+ await queryClient.cancelQueries({ queryKey: api.listItems.queryKey() });
1151
+
1152
+ // Snapshot the previous value
1153
+ const previousItems = queryClient.getQueryData(api.listItems.queryKey());
1154
+
1155
+ // Optimistically update to the new value
1156
+ queryClient.setQueryData(
1157
+ api.listItems.queryKey(),
1158
+ (old) => old.filter((item) => item.id !== itemId)
1159
+ );
1160
+
1161
+ // Return a context object with the snapshot
1162
+ return { previousItems };
1163
+ },
1164
+ onError: (err, itemId, context) => {
1165
+ // If the mutation fails, use the context returned from onMutate to roll back
1166
+ queryClient.setQueryData(api.listItems.queryKey(), context.previousItems);
1167
+ console.error('Failed to delete item:', err);
1168
+ },
1169
+ onSettled: () => {
1170
+ // Always refetch after error or success to ensure data is in sync with server
1171
+ queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() });
1172
+ },
1173
+ });
1174
+
1175
+ if (itemsQuery.isLoading) {
1176
+ return <LoadingSpinner />;
1177
+ }
1178
+
1179
+ if (itemsQuery.isError) {
1180
+ return <ErrorMessage message="Failed to load items" />;
1181
+ }
1182
+
1183
+ return (
1184
+ <ul>
1185
+ {itemsQuery.data.map((item) => (
1186
+ <li key={item.id}>
1187
+ {item.name}
1188
+ <button
1189
+ onClick={() => deleteMutation.mutate(item.id)}
1190
+ disabled={deleteMutation.isPending}
1191
+ >
1192
+ {deleteMutation.isPending ? 'Deleting...' : 'Delete'}
1193
+ </button>
1194
+ </li>
1195
+ ))}
1196
+ </ul>
1197
+ );
1198
+ }
1199
+ ```
1200
+
1201
+ <Drawer title="Optimistic updates using the API client directly" trigger="Click here for an example using the vanilla client directly.">
1202
+ ```tsx
1203
+ function ItemList() {
1204
+ const api = useMyApiClient();
1205
+ const [items, setItems] = useState([]);
1206
+
1207
+ const handleDelete = async (itemId) => {
1208
+ // Optimistically remove the item
1209
+ const previousItems = items;
1210
+ setItems(items.filter((item) => item.id !== itemId));
1211
+
1212
+ try {
1213
+ await api.deleteItem(itemId);
1214
+ } catch (error) {
1215
+ // Restore previous items on error
1216
+ setItems(previousItems);
1217
+ console.error('Failed to delete item:', error);
1218
+ }
1219
+ };
1220
+
1221
+ return (
1222
+ <ul>
1223
+ {items.map((item) => (
1224
+ <li key={item.id}>
1225
+ {item.name}
1226
+ <button onClick={() => handleDelete(item.id)}>Delete</button>
1227
+ </li>
1228
+ ))}
1229
+ </ul>
1230
+ );
1231
+ }
1232
+ ```
1233
+ </Drawer>
1234
+
1235
+ ## Type Safety
1236
+
1237
+ The integration provides complete end-to-end type safety. Your IDE will provide full autocompletion and type checking for all your API calls:
1238
+
1239
+ ```tsx
1240
+ import { useMutation } from '@tanstack/react-query';
1241
+
1242
+ function ItemForm() {
1243
+ const api = useMyApi();
1244
+
1245
+ // Type-safe mutation for creating items
1246
+ const createItem = useMutation({
1247
+ ...api.createItem.mutationOptions(),
1248
+ // ✅ Type error if onSuccess callback doesn't handle the correct response type
1249
+ onSuccess: (data) => {
1250
+ // data is fully typed based on your API's response schema
1251
+ console.log(`Item created with ID: ${data.id}`);
1252
+ },
1253
+ });
1254
+
1255
+ const handleSubmit = (data: CreateItemInput) => {
1256
+ // ✅ Type error if input doesn't match schema
1257
+ createItem.mutate(data);
1258
+ };
1259
+
1260
+ // Error UI can use type narrowing to handle different error types
1261
+ if (createItem.error) {
1262
+ const error = createItem.error;
1263
+ switch (error.status) {
1264
+ case 400:
1265
+ // error.error is typed as InvalidRequestError
1266
+ return (
1267
+ <FormError
1268
+ message="Invalid input"
1269
+ errors={error.error.fieldErrors}
1270
+ />
1271
+ );
1272
+ case 403:
1273
+ // error.error is typed as UnauthorizedError
1274
+ return <AuthError reason={error.error.reason} />;
1275
+ default:
1276
+ // error.error is typed as InternalServerError for 500, etc.
1277
+ return <ServerError message={error.error.message} />;
1278
+ }
1279
+ }
1280
+
1281
+ return (
1282
+ <form onSubmit={(e) => {
1283
+ e.preventDefault();
1284
+ handleSubmit({ name: 'New Item' });
1285
+ }}>
1286
+ {/* Form fields */}
1287
+ <button
1288
+ type="submit"
1289
+ disabled={createItem.isPending}
1290
+ >
1291
+ {createItem.isPending ? 'Creating...' : 'Create Item'}
1292
+ </button>
1293
+ </form>
1294
+ );
1295
+ }
1296
+ ```
1297
+
1298
+ <Drawer title="Type safety using the API client directly" trigger="Click here for an example using the vanilla client directly.">
1299
+ ```tsx
1300
+ function ItemForm() {
1301
+ const api = useMyApiClient();
1302
+ const [error, setError] = useState<CreateItemError | null>(null);
1303
+
1304
+ const handleSubmit = async (data: CreateItemInput) => {
1305
+ try {
1306
+ // ✅ Type error if input doesn't match schema
1307
+ await api.createItem(data);
1308
+ } catch (e) {
1309
+ // ✅ Error type includes all possible error responses
1310
+ const err = e as CreateItemError;
1311
+ switch (err.status) {
1312
+ case 400:
1313
+ // err.error is typed as InvalidRequestError
1314
+ console.error('Validation errors:', err.error.fieldErrors);
1315
+ break;
1316
+ case 403:
1317
+ // err.error is typed as UnauthorizedError
1318
+ console.error('Not authorized:', err.error.reason);
1319
+ break;
1320
+ case 500:
1321
+ // err.error is typed as InternalServerError
1322
+ console.error('Server error:', err.error.message);
1323
+ break;
1324
+ }
1325
+ setError(err);
1326
+ }
1327
+ };
1328
+
1329
+ // Error UI can use type narrowing to handle different error types
1330
+ if (error) {
1331
+ switch (error.status) {
1332
+ case 400:
1333
+ return (
1334
+ <FormError
1335
+ message="Invalid input"
1336
+ errors={error.error.fieldErrors}
1337
+ />
1338
+ );
1339
+ case 403:
1340
+ return <AuthError reason={error.error.reason} />;
1341
+ default:
1342
+ return <ServerError message={error.error.message} />;
1343
+ }
1344
+ }
1345
+
1346
+ return <form onSubmit={handleSubmit}>{/* ... */}</form>;
1347
+ }
1348
+ ```
1349
+ </Drawer>
1350
+
1351
+ The types are automatically generated from your Smithy API's OpenAPI schema, ensuring that any changes to your API are reflected in your frontend code after a build.
1352
+
1353
+ ## Custom Auth
1354
+
1355
+ If your Smithy API uses `Custom` authentication (Lambda Authorizer), you will need to edit the generated client provider to add the authorization headers your authorizer expects. Look for the `fetch` configuration in the generated `<ApiName>Provider.tsx` and add your token or API key to the request headers.