@intrig/plugin-react 0.0.1

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 (55) hide show
  1. package/.swcrc +29 -0
  2. package/README.md +7 -0
  3. package/eslint.config.mjs +19 -0
  4. package/package.json +25 -0
  5. package/project.json +29 -0
  6. package/rollup.config.cjs +54 -0
  7. package/rollup.config.mjs +33 -0
  8. package/src/index.ts +2 -0
  9. package/src/lib/code-generator.ts +79 -0
  10. package/src/lib/get-endpoint-documentation.ts +35 -0
  11. package/src/lib/get-schema-documentation.ts +11 -0
  12. package/src/lib/internal-types.ts +15 -0
  13. package/src/lib/plugin-react.ts +22 -0
  14. package/src/lib/templates/context.template.ts +74 -0
  15. package/src/lib/templates/docs/__snapshots__/async-hook.spec.ts.snap +889 -0
  16. package/src/lib/templates/docs/__snapshots__/download-hook.spec.ts.snap +1445 -0
  17. package/src/lib/templates/docs/__snapshots__/react-hook.spec.ts.snap +1371 -0
  18. package/src/lib/templates/docs/__snapshots__/sse-hook.spec.ts.snap +2008 -0
  19. package/src/lib/templates/docs/async-hook.spec.ts +92 -0
  20. package/src/lib/templates/docs/async-hook.ts +226 -0
  21. package/src/lib/templates/docs/download-hook.spec.ts +182 -0
  22. package/src/lib/templates/docs/download-hook.ts +170 -0
  23. package/src/lib/templates/docs/react-hook.spec.ts +97 -0
  24. package/src/lib/templates/docs/react-hook.ts +323 -0
  25. package/src/lib/templates/docs/schema.ts +105 -0
  26. package/src/lib/templates/docs/sse-hook.spec.ts +207 -0
  27. package/src/lib/templates/docs/sse-hook.ts +221 -0
  28. package/src/lib/templates/extra.template.ts +198 -0
  29. package/src/lib/templates/index.template.ts +14 -0
  30. package/src/lib/templates/intrigMiddleware.template.ts +21 -0
  31. package/src/lib/templates/logger.template.ts +67 -0
  32. package/src/lib/templates/media-type-utils.template.ts +191 -0
  33. package/src/lib/templates/network-state.template.ts +702 -0
  34. package/src/lib/templates/packageJson.template.ts +63 -0
  35. package/src/lib/templates/provider/__tests__/provider-templates.spec.ts +209 -0
  36. package/src/lib/templates/provider/axios-config.template.ts +49 -0
  37. package/src/lib/templates/provider/hooks.template.ts +240 -0
  38. package/src/lib/templates/provider/interfaces.template.ts +72 -0
  39. package/src/lib/templates/provider/intrig-provider-stub.template.ts +73 -0
  40. package/src/lib/templates/provider/intrig-provider.template.ts +185 -0
  41. package/src/lib/templates/provider/main.template.ts +48 -0
  42. package/src/lib/templates/provider/reducer.template.ts +50 -0
  43. package/src/lib/templates/provider/status-trap.template.ts +80 -0
  44. package/src/lib/templates/provider.template.ts +698 -0
  45. package/src/lib/templates/source/controller/method/asyncFunctionHook.template.ts +196 -0
  46. package/src/lib/templates/source/controller/method/clientIndex.template.ts +38 -0
  47. package/src/lib/templates/source/controller/method/download.template.ts +256 -0
  48. package/src/lib/templates/source/controller/method/params.template.ts +31 -0
  49. package/src/lib/templates/source/controller/method/requestHook.template.ts +220 -0
  50. package/src/lib/templates/source/type/typeTemplate.ts +257 -0
  51. package/src/lib/templates/swcrc.template.ts +25 -0
  52. package/src/lib/templates/tsconfig.template.ts +37 -0
  53. package/src/lib/templates/type-utils.template.ts +28 -0
  54. package/tsconfig.json +13 -0
  55. package/tsconfig.lib.json +20 -0
@@ -0,0 +1,1371 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`reactHookDocs > generates markdown that matches snapshot for a simple REST descriptor (no body, no path params) 1`] = `
4
+ "# Intrig React Hooks — Quick Guide
5
+
6
+ ## Copy-paste starter (fast lane)
7
+
8
+ ### 1) Hook import
9
+
10
+ \`\`\`ts
11
+ import { useGetUser } from "@intrig/react/users/client";
12
+ \`\`\`
13
+
14
+ ### 2) Utility guards
15
+
16
+ \`\`\`ts
17
+ import { isPending, isError, isSuccess } from "@intrig/react";
18
+ \`\`\`
19
+
20
+ ### 3) Hook instance (auto-clear on unmount)
21
+
22
+ \`\`\`ts
23
+ const [getUserResp, getUser] = useGetUser({ clearOnUnmount: true });
24
+ \`\`\`
25
+
26
+ Intrig stateful hooks expose a **NetworkState** plus **actions** to fetch / clear.
27
+ Use this when you want a cached, reusable result tied to a global store.
28
+
29
+ ---
30
+
31
+ ## TL;DR (copy–paste)
32
+
33
+ \`\`\`tsx
34
+ import { useGetUser } from "@intrig/react/users/client";
35
+ import { isPending, isError, isSuccess } from "@intrig/react";
36
+ import { useEffect, useMemo } from "react";
37
+
38
+ export default function Example() {
39
+ const [getUserResp, getUser] = useGetUser({ clearOnUnmount: true });
40
+
41
+ useEffect(() => {
42
+ getUser({});
43
+ }, [getUser]);
44
+
45
+ const getUserData = useMemo(
46
+ () => (isSuccess(getUserResp) ? getUserResp.data : undefined),
47
+ [getUserResp],
48
+ );
49
+
50
+ if (isPending(getUserResp)) return <>Loading…</>;
51
+ if (isError(getUserResp)) return <>Error: {String(getUserResp.error)}</>;
52
+ return <pre>{JSON.stringify(getUserData, null, 2)}</pre>;
53
+ }
54
+ \`\`\`
55
+
56
+ ---
57
+
58
+ ## Hook API
59
+
60
+ \`\`\`ts
61
+ // Options are consistent across hooks.
62
+ type UseHookOptions = {
63
+ /** Execute once after mount with provided params/body (if required). */
64
+ fetchOnMount?: boolean;
65
+ /** Reset the state on unmount (recommended). */
66
+ clearOnUnmount?: boolean;
67
+ /** Distinguish multiple instances of the same hook. */
68
+ key?: string;
69
+ /** Initial path params for endpoints that require them. */
70
+ params?: unknown;
71
+ /** Initial request body (for POST/PUT/etc.). */
72
+ body?: unknown;
73
+ };
74
+
75
+ // Prefer concrete types if your build emits them:
76
+ // import type { GetUserResponseBody } from '@intrig/react/users/GetUser.response';
77
+
78
+ type GetUserData = GetUserResponseBody; // replace with GetUserResponseBody if generated
79
+ type GetUserRequest = { params?: unknown; body?: unknown };
80
+
81
+ // Signature (shape shown; concrete generics vary per generated hook)
82
+ declare function useGetUser(
83
+ options?: UseHookOptions,
84
+ ): [NetworkState<GetUserData>, (req: GetUserRequest) => void, () => void];
85
+ \`\`\`
86
+
87
+ ### NetworkState & guards
88
+
89
+ \`\`\`ts
90
+ import { isPending, isError, isSuccess } from "@intrig/react";
91
+ \`\`\`
92
+
93
+ ---
94
+
95
+ ## Conceptual model (Stateful = single source of truth)
96
+
97
+ - Lives in a shared store keyed by \`key\` + request signature.
98
+ - Best for **read** endpoints you want to **reuse** or keep **warm** (lists, details, search).
99
+ - Lifecycle helpers:
100
+ - \`fetchOnMount\` to kick off automatically.
101
+ - \`clearOnUnmount\` to clean up (recommended default).
102
+ - \`key\` to isolate multiple independent instances.
103
+
104
+ ---
105
+
106
+ ## Usage patterns
107
+
108
+ ### 1) Controlled (most explicit)
109
+
110
+ \`\`\`tsx
111
+ const [getUserResp, getUser, clearGetUser] = useGetUser();
112
+
113
+ useEffect(() => {
114
+ getUser({});
115
+ return clearGetUser; // optional cleanup
116
+ }, [getUser, clearGetUser]);
117
+ \`\`\`
118
+
119
+ <details><summary>Description</summary>
120
+ <p><strong>Use when</strong> you need explicit control over when a request fires, what params/body it uses, and when to clean up. Ideal for search forms, pagination, conditional fetches.</p>
121
+ </details>
122
+
123
+ ### 2) Lifecycle-bound (shorthand)
124
+
125
+ \`\`\`tsx
126
+ const [getUserResp] = useGetUser({
127
+ fetchOnMount: true,
128
+ clearOnUnmount: true,
129
+ params: {},
130
+ });
131
+ \`\`\`
132
+
133
+ <details><summary>Description</summary>
134
+ <p><strong>Use when</strong> the data should follow the component lifecycle: fetch once on mount, reset on unmount.</p>
135
+ </details>
136
+
137
+ ### 3) Passive observer (render when data arrives)
138
+
139
+ \`\`\`tsx
140
+ const [getUserResp] = useGetUser();
141
+ return isSuccess(getUserResp) ? <>{String(getUserResp.data)}</> : null;
142
+ \`\`\`
143
+
144
+ <details><summary>Description</summary>
145
+ <p><strong>Use when</strong> another part of the app triggers the fetch and this component only reads the state.</p>
146
+ </details>
147
+
148
+ ### 4) Keep previous success while refetching (sticky)
149
+
150
+ \`\`\`tsx
151
+ const [getUserData, setGetUserData] = useState<any>();
152
+ const [getUserResp, getUser] = useGetUser();
153
+
154
+ useEffect(() => {
155
+ if (isSuccess(getUserResp)) setGetUserData(getUserResp.data);
156
+ }, [getUserResp]);
157
+
158
+ return (
159
+ <>
160
+ {isPending(getUserResp) && getUserData ? <div>Refreshing…</div> : null}
161
+ <pre>
162
+ {JSON.stringify(
163
+ isSuccess(getUserResp) ? getUserResp.data : getUserData,
164
+ null,
165
+ 2,
166
+ )}
167
+ </pre>
168
+ </>
169
+ );
170
+ \`\`\`
171
+
172
+ <details><summary>Description</summary>
173
+ <p><strong>Use when</strong> you want SWR-like UX without flicker.</p>
174
+ </details>
175
+
176
+ ### 5) Multiple instances of the same hook (isolate with \`key\`)
177
+
178
+ \`\`\`tsx
179
+ const a = useGetUser({ key: "A", fetchOnMount: true, params: {} });
180
+ const b = useGetUser({ key: "B", fetchOnMount: true, params: {} });
181
+ \`\`\`
182
+
183
+ <details><summary>Description</summary>
184
+ <p>Use unique keys to prevent state collisions.</p>
185
+ </details>
186
+
187
+ ---
188
+
189
+ ## Before / After mini-migrations
190
+
191
+ ### If you mistakenly used Stateful for a simple submit → switch to Async
192
+
193
+ \`\`\`diff
194
+ - const [getUserResp, getUser] = useGetUser();
195
+ - getUser({});
196
+ + const [fn] = useGetUserAsync();
197
+ + await fn();
198
+ \`\`\`
199
+
200
+ ### If you started with Async but need to read later in another component → Stateful
201
+
202
+ \`\`\`diff
203
+ - const [fn] = useGetUserAsync();
204
+ - const data = await fn();
205
+ + const [getUserResp, getUser] = useGetUser({ fetchOnMount: true, clearOnUnmount: true, params: {}, });
206
+ + // read from getUserResp anywhere with useGetUser()
207
+ \`\`\`
208
+
209
+ ---
210
+
211
+ ## Anti-patterns
212
+
213
+ <details><summary>Don’t use Stateful for field validations or a one-off submit</summary>
214
+ Use the Async variant instead: \`const [fn] = useGetUserAsync()\`.
215
+ </details>
216
+ <details><summary>Don’t use Async for long-lived lists or detail views</summary>
217
+ Use Stateful so other components can read the same data and you can avoid refetch churn.
218
+ </details>
219
+ <details><summary>Don’t forget required \`params\` when using \`fetchOnMount\`</summary>
220
+ Provide \`params\` (and \`body\` if applicable) or switch to the controlled pattern.
221
+ </details>
222
+ <details><summary>Rendering the same hook twice without a \`key\`</summary>
223
+ If they should be independent, add a unique \`key\`.
224
+ </details>
225
+
226
+ ---
227
+
228
+ ## Error & UX guidance
229
+
230
+ - **Loading:** early return or inline spinner. Prefer **sticky data** to avoid blanking content.
231
+ - **Errors:** show a banner or inline errors depending on UX; keep previous good state if possible.
232
+ - **Cleanup:** prefer \`clearOnUnmount: true\` as the default.
233
+
234
+ ---
235
+
236
+ ## Concurrency patterns
237
+
238
+ - **Refresh:** call the action again; combine with sticky data for smooth UX.
239
+ - **Dedupe:** isolate instances with \`key\`.
240
+ - **Parallel:** render two keyed instances; don’t share the same key.
241
+
242
+ ---
243
+
244
+ ## Full examples
245
+
246
+ ### Short format (lifecycle-bound)
247
+
248
+ \`\`\`tsx
249
+ import { useGetUser } from "@intrig/react/users/client";
250
+ import { isPending, isError, isSuccess } from "@intrig/react";
251
+ import { useMemo } from "react";
252
+
253
+ function ShortExample() {
254
+ const [getUserResp] = useGetUser({
255
+ fetchOnMount: true,
256
+ clearOnUnmount: true,
257
+ params: {},
258
+ });
259
+
260
+ const getUserData = useMemo(
261
+ () => (isSuccess(getUserResp) ? getUserResp.data : undefined),
262
+ [getUserResp],
263
+ );
264
+
265
+ if (isPending(getUserResp)) return <>Loading…</>;
266
+ if (isError(getUserResp)) return <>Error: {String(getUserResp.error)}</>;
267
+ return <pre>{JSON.stringify(getUserData, null, 2)}</pre>;
268
+ }
269
+ \`\`\`
270
+
271
+ <details><summary>Description</summary>
272
+ <p>Compact lifecycle-bound approach. Great for read-only pages that load once and clean up on unmount.</p>
273
+ </details>
274
+
275
+ ### Controlled format (explicit actions)
276
+
277
+ \`\`\`tsx
278
+ import { useGetUser } from "@intrig/react/users/client";
279
+ import { isPending, isError, isSuccess } from "@intrig/react";
280
+ import { useEffect, useMemo } from "react";
281
+
282
+ function ControlledExample() {
283
+ const [getUserResp, getUser, clearGetUser] = useGetUser();
284
+
285
+ useEffect(() => {
286
+ getUser({});
287
+ return clearGetUser;
288
+ }, [getUser, clearGetUser]);
289
+
290
+ const getUserData = useMemo(
291
+ () => (isSuccess(getUserResp) ? getUserResp.data : undefined),
292
+ [getUserResp],
293
+ );
294
+
295
+ if (isPending(getUserResp)) return <>Loading…</>;
296
+ if (isError(getUserResp))
297
+ return <>An error occurred: {String(getUserResp.error)}</>;
298
+ return <pre>{JSON.stringify(getUserData, null, 2)}</pre>;
299
+ }
300
+ \`\`\`
301
+
302
+ ---
303
+
304
+ ## Gotchas & Tips
305
+
306
+ - Prefer **\`clearOnUnmount: true\`** in most components.
307
+ - Use **\`key\`** for multiple independent instances.
308
+ - Memoize derived values with **\`useMemo\`** to avoid churn.
309
+ - Inline indicators keep the rest of the page interactive.
310
+
311
+ ---
312
+
313
+ ## Reference: State helpers
314
+
315
+ \`\`\`ts
316
+ if (isPending(getUserResp)) {
317
+ /* show spinner */
318
+ }
319
+ if (isError(getUserResp)) {
320
+ /* show error */
321
+ }
322
+ if (isSuccess(getUserResp)) {
323
+ /* read getUserResp.data */
324
+ }
325
+ \`\`\`
326
+ "
327
+ `;
328
+
329
+ exports[`reactHookDocs > snapshot — path params only (no request body) 1`] = `
330
+ "# Intrig React Hooks — Quick Guide
331
+
332
+ ## Copy-paste starter (fast lane)
333
+
334
+ ### 1) Hook import
335
+
336
+ \`\`\`ts
337
+ import { useGetUserById } from "@intrig/react/users/{id}/client";
338
+ \`\`\`
339
+
340
+ ### 2) Utility guards
341
+
342
+ \`\`\`ts
343
+ import { isPending, isError, isSuccess } from "@intrig/react";
344
+ \`\`\`
345
+
346
+ ### 3) Hook instance (auto-clear on unmount)
347
+
348
+ \`\`\`ts
349
+ const [getUserByIdResp, getUserById] = useGetUserById({ clearOnUnmount: true });
350
+ \`\`\`
351
+
352
+ Intrig stateful hooks expose a **NetworkState** plus **actions** to fetch / clear.
353
+ Use this when you want a cached, reusable result tied to a global store.
354
+
355
+ ---
356
+
357
+ ## TL;DR (copy–paste)
358
+
359
+ \`\`\`tsx
360
+ import { useGetUserById } from "@intrig/react/users/{id}/client";
361
+ import { isPending, isError, isSuccess } from "@intrig/react";
362
+ import { useEffect, useMemo } from "react";
363
+
364
+ export default function Example() {
365
+ const [getUserByIdResp, getUserById] = useGetUserById({
366
+ clearOnUnmount: true,
367
+ });
368
+
369
+ useEffect(() => {
370
+ getUserById(getUserByIdParams);
371
+ }, [getUserById, getUserByIdParams]);
372
+
373
+ const getUserByIdData = useMemo(
374
+ () => (isSuccess(getUserByIdResp) ? getUserByIdResp.data : undefined),
375
+ [getUserByIdResp],
376
+ );
377
+
378
+ if (isPending(getUserByIdResp)) return <>Loading…</>;
379
+ if (isError(getUserByIdResp))
380
+ return <>Error: {String(getUserByIdResp.error)}</>;
381
+ return <pre>{JSON.stringify(getUserByIdData, null, 2)}</pre>;
382
+ }
383
+ \`\`\`
384
+
385
+ ### Optional types (if generated by your build)
386
+
387
+ \`\`\`ts
388
+ import type { GetUserByIdParams } from "@intrig/react/users/{id}/GetUserById.params";
389
+ // Prefer the concrete response type:
390
+ import type { GetUserByIdResponseBody } from "@intrig/react/users/{id}/GetUserById.response";
391
+ \`\`\`
392
+
393
+ ---
394
+
395
+ ## Hook API
396
+
397
+ \`\`\`ts
398
+ // Options are consistent across hooks.
399
+ type UseHookOptions = {
400
+ /** Execute once after mount with provided params/body (if required). */
401
+ fetchOnMount?: boolean;
402
+ /** Reset the state on unmount (recommended). */
403
+ clearOnUnmount?: boolean;
404
+ /** Distinguish multiple instances of the same hook. */
405
+ key?: string;
406
+ /** Initial path params for endpoints that require them. */
407
+ params?: GetUserByIdParams;
408
+ /** Initial request body (for POST/PUT/etc.). */
409
+ body?: unknown;
410
+ };
411
+
412
+ // Prefer concrete types if your build emits them:
413
+ // import type { GetUserByIdResponseBody } from '@intrig/react/users/{id}/GetUserById.response';
414
+
415
+ type GetUserByIdData = GetUserByIdResponseBody; // replace with GetUserByIdResponseBody if generated
416
+ type GetUserByIdRequest = { params?: GetUserByIdParams; body?: unknown };
417
+
418
+ // Signature (shape shown; concrete generics vary per generated hook)
419
+ declare function useGetUserById(
420
+ options?: UseHookOptions,
421
+ ): [
422
+ NetworkState<GetUserByIdData>,
423
+ (req: GetUserByIdRequest) => void,
424
+ () => void,
425
+ ];
426
+ \`\`\`
427
+
428
+ ### NetworkState & guards
429
+
430
+ \`\`\`ts
431
+ import { isPending, isError, isSuccess } from "@intrig/react";
432
+ \`\`\`
433
+
434
+ ---
435
+
436
+ ## Conceptual model (Stateful = single source of truth)
437
+
438
+ - Lives in a shared store keyed by \`key\` + request signature.
439
+ - Best for **read** endpoints you want to **reuse** or keep **warm** (lists, details, search).
440
+ - Lifecycle helpers:
441
+ - \`fetchOnMount\` to kick off automatically.
442
+ - \`clearOnUnmount\` to clean up (recommended default).
443
+ - \`key\` to isolate multiple independent instances.
444
+
445
+ ---
446
+
447
+ ## Usage patterns
448
+
449
+ ### 1) Controlled (most explicit)
450
+
451
+ \`\`\`tsx
452
+ const [getUserByIdResp, getUserById, clearGetUserById] = useGetUserById();
453
+
454
+ useEffect(() => {
455
+ getUserById(getUserByIdParams);
456
+ return clearGetUserById; // optional cleanup
457
+ }, [getUserById, clearGetUserById]);
458
+ \`\`\`
459
+
460
+ <details><summary>Description</summary>
461
+ <p><strong>Use when</strong> you need explicit control over when a request fires, what params/body it uses, and when to clean up. Ideal for search forms, pagination, conditional fetches.</p>
462
+ </details>
463
+
464
+ ### 2) Lifecycle-bound (shorthand)
465
+
466
+ \`\`\`tsx
467
+ const [getUserByIdResp] = useGetUserById({
468
+ fetchOnMount: true,
469
+ clearOnUnmount: true,
470
+ params: getUserByIdParams,
471
+ });
472
+ \`\`\`
473
+
474
+ <details><summary>Description</summary>
475
+ <p><strong>Use when</strong> the data should follow the component lifecycle: fetch once on mount, reset on unmount.</p>
476
+ </details>
477
+
478
+ ### 3) Passive observer (render when data arrives)
479
+
480
+ \`\`\`tsx
481
+ const [getUserByIdResp] = useGetUserById();
482
+ return isSuccess(getUserByIdResp) ? <>{String(getUserByIdResp.data)}</> : null;
483
+ \`\`\`
484
+
485
+ <details><summary>Description</summary>
486
+ <p><strong>Use when</strong> another part of the app triggers the fetch and this component only reads the state.</p>
487
+ </details>
488
+
489
+ ### 4) Keep previous success while refetching (sticky)
490
+
491
+ \`\`\`tsx
492
+ const [getUserByIdData, setGetUserByIdData] = useState<any>();
493
+ const [getUserByIdResp, getUserById] = useGetUserById();
494
+
495
+ useEffect(() => {
496
+ if (isSuccess(getUserByIdResp)) setGetUserByIdData(getUserByIdResp.data);
497
+ }, [getUserByIdResp]);
498
+
499
+ return (
500
+ <>
501
+ {isPending(getUserByIdResp) && getUserByIdData ? (
502
+ <div>Refreshing…</div>
503
+ ) : null}
504
+ <pre>
505
+ {JSON.stringify(
506
+ isSuccess(getUserByIdResp) ? getUserByIdResp.data : getUserByIdData,
507
+ null,
508
+ 2,
509
+ )}
510
+ </pre>
511
+ </>
512
+ );
513
+ \`\`\`
514
+
515
+ <details><summary>Description</summary>
516
+ <p><strong>Use when</strong> you want SWR-like UX without flicker.</p>
517
+ </details>
518
+
519
+ ### 5) Multiple instances of the same hook (isolate with \`key\`)
520
+
521
+ \`\`\`tsx
522
+ const a = useGetUserById({
523
+ key: "A",
524
+ fetchOnMount: true,
525
+ params: getUserByIdParams,
526
+ });
527
+ const b = useGetUserById({
528
+ key: "B",
529
+ fetchOnMount: true,
530
+ params: getUserByIdParams,
531
+ });
532
+ \`\`\`
533
+
534
+ <details><summary>Description</summary>
535
+ <p>Use unique keys to prevent state collisions.</p>
536
+ </details>
537
+
538
+ ---
539
+
540
+ ## Before / After mini-migrations
541
+
542
+ ### If you mistakenly used Stateful for a simple submit → switch to Async
543
+
544
+ \`\`\`diff
545
+ - const [getUserByIdResp, getUserById] = useGetUserById();
546
+ - getUserById(getUserByIdParams);
547
+ + const [fn] = useGetUserByIdAsync();
548
+ + await fn(getUserByIdParams);
549
+ \`\`\`
550
+
551
+ ### If you started with Async but need to read later in another component → Stateful
552
+
553
+ \`\`\`diff
554
+ - const [fn] = useGetUserByIdAsync();
555
+ - const data = await fn(getUserByIdParams);
556
+ + const [getUserByIdResp, getUserById] = useGetUserById({ fetchOnMount: true, clearOnUnmount: true, params: getUserByIdParams, });
557
+ + // read from getUserByIdResp anywhere with useGetUserById()
558
+ \`\`\`
559
+
560
+ ---
561
+
562
+ ## Anti-patterns
563
+
564
+ <details><summary>Don’t use Stateful for field validations or a one-off submit</summary>
565
+ Use the Async variant instead: \`const [fn] = useGetUserByIdAsync()\`.
566
+ </details>
567
+ <details><summary>Don’t use Async for long-lived lists or detail views</summary>
568
+ Use Stateful so other components can read the same data and you can avoid refetch churn.
569
+ </details>
570
+ <details><summary>Don’t forget required \`params\` when using \`fetchOnMount\`</summary>
571
+ Provide \`params\` (and \`body\` if applicable) or switch to the controlled pattern.
572
+ </details>
573
+ <details><summary>Rendering the same hook twice without a \`key\`</summary>
574
+ If they should be independent, add a unique \`key\`.
575
+ </details>
576
+
577
+ ---
578
+
579
+ ## Error & UX guidance
580
+
581
+ - **Loading:** early return or inline spinner. Prefer **sticky data** to avoid blanking content.
582
+ - **Errors:** show a banner or inline errors depending on UX; keep previous good state if possible.
583
+ - **Cleanup:** prefer \`clearOnUnmount: true\` as the default.
584
+
585
+ ---
586
+
587
+ ## Concurrency patterns
588
+
589
+ - **Refresh:** call the action again; combine with sticky data for smooth UX.
590
+ - **Dedupe:** isolate instances with \`key\`.
591
+ - **Parallel:** render two keyed instances; don’t share the same key.
592
+
593
+ ---
594
+
595
+ ## Full examples
596
+
597
+ ### Short format (lifecycle-bound)
598
+
599
+ \`\`\`tsx
600
+ import { useGetUserById } from "@intrig/react/users/{id}/client";
601
+ import { isPending, isError, isSuccess } from "@intrig/react";
602
+ import { useMemo } from "react";
603
+
604
+ function ShortExample() {
605
+ const [getUserByIdResp] = useGetUserById({
606
+ fetchOnMount: true,
607
+ clearOnUnmount: true,
608
+ params: getUserByIdParams,
609
+ });
610
+
611
+ const getUserByIdData = useMemo(
612
+ () => (isSuccess(getUserByIdResp) ? getUserByIdResp.data : undefined),
613
+ [getUserByIdResp],
614
+ );
615
+
616
+ if (isPending(getUserByIdResp)) return <>Loading…</>;
617
+ if (isError(getUserByIdResp))
618
+ return <>Error: {String(getUserByIdResp.error)}</>;
619
+ return <pre>{JSON.stringify(getUserByIdData, null, 2)}</pre>;
620
+ }
621
+ \`\`\`
622
+
623
+ <details><summary>Description</summary>
624
+ <p>Compact lifecycle-bound approach. Great for read-only pages that load once and clean up on unmount.</p>
625
+ </details>
626
+
627
+ ### Controlled format (explicit actions)
628
+
629
+ \`\`\`tsx
630
+ import { useGetUserById } from "@intrig/react/users/{id}/client";
631
+ import { isPending, isError, isSuccess } from "@intrig/react";
632
+ import { useEffect, useMemo } from "react";
633
+
634
+ function ControlledExample() {
635
+ const [getUserByIdResp, getUserById, clearGetUserById] = useGetUserById();
636
+
637
+ useEffect(() => {
638
+ getUserById(getUserByIdParams);
639
+ return clearGetUserById;
640
+ }, [getUserById, clearGetUserById]);
641
+
642
+ const getUserByIdData = useMemo(
643
+ () => (isSuccess(getUserByIdResp) ? getUserByIdResp.data : undefined),
644
+ [getUserByIdResp],
645
+ );
646
+
647
+ if (isPending(getUserByIdResp)) return <>Loading…</>;
648
+ if (isError(getUserByIdResp))
649
+ return <>An error occurred: {String(getUserByIdResp.error)}</>;
650
+ return <pre>{JSON.stringify(getUserByIdData, null, 2)}</pre>;
651
+ }
652
+ \`\`\`
653
+
654
+ ---
655
+
656
+ ## Gotchas & Tips
657
+
658
+ - Prefer **\`clearOnUnmount: true\`** in most components.
659
+ - Use **\`key\`** for multiple independent instances.
660
+ - Memoize derived values with **\`useMemo\`** to avoid churn.
661
+ - Inline indicators keep the rest of the page interactive.
662
+
663
+ ---
664
+
665
+ ## Reference: State helpers
666
+
667
+ \`\`\`ts
668
+ if (isPending(getUserByIdResp)) {
669
+ /* show spinner */
670
+ }
671
+ if (isError(getUserByIdResp)) {
672
+ /* show error */
673
+ }
674
+ if (isSuccess(getUserByIdResp)) {
675
+ /* read getUserByIdResp.data */
676
+ }
677
+ \`\`\`
678
+ "
679
+ `;
680
+
681
+ exports[`reactHookDocs > snapshot — request body and path params 1`] = `
682
+ "# Intrig React Hooks — Quick Guide
683
+
684
+ ## Copy-paste starter (fast lane)
685
+
686
+ ### 1) Hook import
687
+
688
+ \`\`\`ts
689
+ import { useUpdateUser } from "@intrig/react/users/{id}/client";
690
+ \`\`\`
691
+
692
+ ### 2) Utility guards
693
+
694
+ \`\`\`ts
695
+ import { isPending, isError, isSuccess } from "@intrig/react";
696
+ \`\`\`
697
+
698
+ ### 3) Hook instance (auto-clear on unmount)
699
+
700
+ \`\`\`ts
701
+ const [updateUserResp, updateUser] = useUpdateUser({ clearOnUnmount: true });
702
+ \`\`\`
703
+
704
+ Intrig stateful hooks expose a **NetworkState** plus **actions** to fetch / clear.
705
+ Use this when you want a cached, reusable result tied to a global store.
706
+
707
+ ---
708
+
709
+ ## TL;DR (copy–paste)
710
+
711
+ \`\`\`tsx
712
+ import { useUpdateUser } from "@intrig/react/users/{id}/client";
713
+ import { isPending, isError, isSuccess } from "@intrig/react";
714
+ import { useEffect, useMemo } from "react";
715
+
716
+ export default function Example() {
717
+ const [updateUserResp, updateUser] = useUpdateUser({ clearOnUnmount: true });
718
+
719
+ useEffect(() => {
720
+ updateUser(updateUserRequest, updateUserParams);
721
+ }, [updateUser, updateUserRequest, updateUserParams]);
722
+
723
+ const updateUserData = useMemo(
724
+ () => (isSuccess(updateUserResp) ? updateUserResp.data : undefined),
725
+ [updateUserResp],
726
+ );
727
+
728
+ if (isPending(updateUserResp)) return <>Loading…</>;
729
+ if (isError(updateUserResp))
730
+ return <>Error: {String(updateUserResp.error)}</>;
731
+ return <pre>{JSON.stringify(updateUserData, null, 2)}</pre>;
732
+ }
733
+ \`\`\`
734
+
735
+ ### Optional types (if generated by your build)
736
+
737
+ \`\`\`ts
738
+ import type { UpdateUserRequest } from "@intrig/react/demo_api/components/schemas/UpdateUserRequest";
739
+ import type { UpdateUserParams } from "@intrig/react/users/{id}/UpdateUser.params";
740
+ // Prefer the concrete response type:
741
+ import type { UpdateUserResponseBody } from "@intrig/react/users/{id}/UpdateUser.response";
742
+ \`\`\`
743
+
744
+ ---
745
+
746
+ ## Hook API
747
+
748
+ \`\`\`ts
749
+ // Options are consistent across hooks.
750
+ type UseHookOptions = {
751
+ /** Execute once after mount with provided params/body (if required). */
752
+ fetchOnMount?: boolean;
753
+ /** Reset the state on unmount (recommended). */
754
+ clearOnUnmount?: boolean;
755
+ /** Distinguish multiple instances of the same hook. */
756
+ key?: string;
757
+ /** Initial path params for endpoints that require them. */
758
+ params?: UpdateUserParams;
759
+ /** Initial request body (for POST/PUT/etc.). */
760
+ body?: UpdateUserRequest;
761
+ };
762
+
763
+ // Prefer concrete types if your build emits them:
764
+ // import type { UpdateUserResponseBody } from '@intrig/react/users/{id}/UpdateUser.response';
765
+
766
+ type UpdateUserData = UpdateUserResponseBody; // replace with UpdateUserResponseBody if generated
767
+ type UpdateUserRequest = {
768
+ params?: UpdateUserParams;
769
+ body?: UpdateUserRequest;
770
+ };
771
+
772
+ // Signature (shape shown; concrete generics vary per generated hook)
773
+ declare function useUpdateUser(
774
+ options?: UseHookOptions,
775
+ ): [NetworkState<UpdateUserData>, (req: UpdateUserRequest) => void, () => void];
776
+ \`\`\`
777
+
778
+ ### NetworkState & guards
779
+
780
+ \`\`\`ts
781
+ import { isPending, isError, isSuccess } from "@intrig/react";
782
+ \`\`\`
783
+
784
+ ---
785
+
786
+ ## Conceptual model (Stateful = single source of truth)
787
+
788
+ - Lives in a shared store keyed by \`key\` + request signature.
789
+ - Best for **read** endpoints you want to **reuse** or keep **warm** (lists, details, search).
790
+ - Lifecycle helpers:
791
+ - \`fetchOnMount\` to kick off automatically.
792
+ - \`clearOnUnmount\` to clean up (recommended default).
793
+ - \`key\` to isolate multiple independent instances.
794
+
795
+ ---
796
+
797
+ ## Usage patterns
798
+
799
+ ### 1) Controlled (most explicit)
800
+
801
+ \`\`\`tsx
802
+ const [updateUserResp, updateUser, clearUpdateUser] = useUpdateUser();
803
+
804
+ useEffect(() => {
805
+ updateUser(updateUserRequest, updateUserParams);
806
+ return clearUpdateUser; // optional cleanup
807
+ }, [updateUser, clearUpdateUser]);
808
+ \`\`\`
809
+
810
+ <details><summary>Description</summary>
811
+ <p><strong>Use when</strong> you need explicit control over when a request fires, what params/body it uses, and when to clean up. Ideal for search forms, pagination, conditional fetches.</p>
812
+ </details>
813
+
814
+ ### 2) Lifecycle-bound (shorthand)
815
+
816
+ \`\`\`tsx
817
+ const [updateUserResp] = useUpdateUser({
818
+ fetchOnMount: true,
819
+ clearOnUnmount: true,
820
+ body: updateUserRequest,
821
+ params: updateUserParams,
822
+ });
823
+ \`\`\`
824
+
825
+ <details><summary>Description</summary>
826
+ <p><strong>Use when</strong> the data should follow the component lifecycle: fetch once on mount, reset on unmount.</p>
827
+ </details>
828
+
829
+ ### 3) Passive observer (render when data arrives)
830
+
831
+ \`\`\`tsx
832
+ const [updateUserResp] = useUpdateUser();
833
+ return isSuccess(updateUserResp) ? <>{String(updateUserResp.data)}</> : null;
834
+ \`\`\`
835
+
836
+ <details><summary>Description</summary>
837
+ <p><strong>Use when</strong> another part of the app triggers the fetch and this component only reads the state.</p>
838
+ </details>
839
+
840
+ ### 4) Keep previous success while refetching (sticky)
841
+
842
+ \`\`\`tsx
843
+ const [updateUserData, setUpdateUserData] = useState<any>();
844
+ const [updateUserResp, updateUser] = useUpdateUser();
845
+
846
+ useEffect(() => {
847
+ if (isSuccess(updateUserResp)) setUpdateUserData(updateUserResp.data);
848
+ }, [updateUserResp]);
849
+
850
+ return (
851
+ <>
852
+ {isPending(updateUserResp) && updateUserData ? (
853
+ <div>Refreshing…</div>
854
+ ) : null}
855
+ <pre>
856
+ {JSON.stringify(
857
+ isSuccess(updateUserResp) ? updateUserResp.data : updateUserData,
858
+ null,
859
+ 2,
860
+ )}
861
+ </pre>
862
+ </>
863
+ );
864
+ \`\`\`
865
+
866
+ <details><summary>Description</summary>
867
+ <p><strong>Use when</strong> you want SWR-like UX without flicker.</p>
868
+ </details>
869
+
870
+ ### 5) Multiple instances of the same hook (isolate with \`key\`)
871
+
872
+ \`\`\`tsx
873
+ const a = useUpdateUser({
874
+ key: "A",
875
+ fetchOnMount: true,
876
+ params: updateUserParams,
877
+ });
878
+ const b = useUpdateUser({
879
+ key: "B",
880
+ fetchOnMount: true,
881
+ params: updateUserParams,
882
+ });
883
+ \`\`\`
884
+
885
+ <details><summary>Description</summary>
886
+ <p>Use unique keys to prevent state collisions.</p>
887
+ </details>
888
+
889
+ ---
890
+
891
+ ## Before / After mini-migrations
892
+
893
+ ### If you mistakenly used Stateful for a simple submit → switch to Async
894
+
895
+ \`\`\`diff
896
+ - const [updateUserResp, updateUser] = useUpdateUser();
897
+ - updateUser(updateUserRequest, updateUserParams);
898
+ + const [fn] = useUpdateUserAsync();
899
+ + await fn(updateUserRequest, updateUserParams);
900
+ \`\`\`
901
+
902
+ ### If you started with Async but need to read later in another component → Stateful
903
+
904
+ \`\`\`diff
905
+ - const [fn] = useUpdateUserAsync();
906
+ - const data = await fn(updateUserRequest, updateUserParams);
907
+ + const [updateUserResp, updateUser] = useUpdateUser({ fetchOnMount: true, clearOnUnmount: true, params: updateUserParams, body: updateUserRequest });
908
+ + // read from updateUserResp anywhere with useUpdateUser()
909
+ \`\`\`
910
+
911
+ ---
912
+
913
+ ## Anti-patterns
914
+
915
+ <details><summary>Don’t use Stateful for field validations or a one-off submit</summary>
916
+ Use the Async variant instead: \`const [fn] = useUpdateUserAsync()\`.
917
+ </details>
918
+ <details><summary>Don’t use Async for long-lived lists or detail views</summary>
919
+ Use Stateful so other components can read the same data and you can avoid refetch churn.
920
+ </details>
921
+ <details><summary>Don’t forget required \`params\` when using \`fetchOnMount\`</summary>
922
+ Provide \`params\` (and \`body\` if applicable) or switch to the controlled pattern.
923
+ </details>
924
+ <details><summary>Rendering the same hook twice without a \`key\`</summary>
925
+ If they should be independent, add a unique \`key\`.
926
+ </details>
927
+
928
+ ---
929
+
930
+ ## Error & UX guidance
931
+
932
+ - **Loading:** early return or inline spinner. Prefer **sticky data** to avoid blanking content.
933
+ - **Errors:** show a banner or inline errors depending on UX; keep previous good state if possible.
934
+ - **Cleanup:** prefer \`clearOnUnmount: true\` as the default.
935
+
936
+ ---
937
+
938
+ ## Concurrency patterns
939
+
940
+ - **Refresh:** call the action again; combine with sticky data for smooth UX.
941
+ - **Dedupe:** isolate instances with \`key\`.
942
+ - **Parallel:** render two keyed instances; don’t share the same key.
943
+
944
+ ---
945
+
946
+ ## Full examples
947
+
948
+ ### Short format (lifecycle-bound)
949
+
950
+ \`\`\`tsx
951
+ import { useUpdateUser } from "@intrig/react/users/{id}/client";
952
+ import { isPending, isError, isSuccess } from "@intrig/react";
953
+ import { useMemo } from "react";
954
+
955
+ function ShortExample() {
956
+ const [updateUserResp] = useUpdateUser({
957
+ fetchOnMount: true,
958
+ clearOnUnmount: true,
959
+ body: updateUserRequest,
960
+ params: updateUserParams,
961
+ });
962
+
963
+ const updateUserData = useMemo(
964
+ () => (isSuccess(updateUserResp) ? updateUserResp.data : undefined),
965
+ [updateUserResp],
966
+ );
967
+
968
+ if (isPending(updateUserResp)) return <>Loading…</>;
969
+ if (isError(updateUserResp))
970
+ return <>Error: {String(updateUserResp.error)}</>;
971
+ return <pre>{JSON.stringify(updateUserData, null, 2)}</pre>;
972
+ }
973
+ \`\`\`
974
+
975
+ <details><summary>Description</summary>
976
+ <p>Compact lifecycle-bound approach. Great for read-only pages that load once and clean up on unmount.</p>
977
+ </details>
978
+
979
+ ### Controlled format (explicit actions)
980
+
981
+ \`\`\`tsx
982
+ import { useUpdateUser } from "@intrig/react/users/{id}/client";
983
+ import { isPending, isError, isSuccess } from "@intrig/react";
984
+ import { useEffect, useMemo } from "react";
985
+
986
+ function ControlledExample() {
987
+ const [updateUserResp, updateUser, clearUpdateUser] = useUpdateUser();
988
+
989
+ useEffect(() => {
990
+ updateUser(updateUserRequest, updateUserParams);
991
+ return clearUpdateUser;
992
+ }, [updateUser, clearUpdateUser]);
993
+
994
+ const updateUserData = useMemo(
995
+ () => (isSuccess(updateUserResp) ? updateUserResp.data : undefined),
996
+ [updateUserResp],
997
+ );
998
+
999
+ if (isPending(updateUserResp)) return <>Loading…</>;
1000
+ if (isError(updateUserResp))
1001
+ return <>An error occurred: {String(updateUserResp.error)}</>;
1002
+ return <pre>{JSON.stringify(updateUserData, null, 2)}</pre>;
1003
+ }
1004
+ \`\`\`
1005
+
1006
+ ---
1007
+
1008
+ ## Gotchas & Tips
1009
+
1010
+ - Prefer **\`clearOnUnmount: true\`** in most components.
1011
+ - Use **\`key\`** for multiple independent instances.
1012
+ - Memoize derived values with **\`useMemo\`** to avoid churn.
1013
+ - Inline indicators keep the rest of the page interactive.
1014
+
1015
+ ---
1016
+
1017
+ ## Reference: State helpers
1018
+
1019
+ \`\`\`ts
1020
+ if (isPending(updateUserResp)) {
1021
+ /* show spinner */
1022
+ }
1023
+ if (isError(updateUserResp)) {
1024
+ /* show error */
1025
+ }
1026
+ if (isSuccess(updateUserResp)) {
1027
+ /* read updateUserResp.data */
1028
+ }
1029
+ \`\`\`
1030
+ "
1031
+ `;
1032
+
1033
+ exports[`reactHookDocs > snapshot — request body only (no path params) 1`] = `
1034
+ "# Intrig React Hooks — Quick Guide
1035
+
1036
+ ## Copy-paste starter (fast lane)
1037
+
1038
+ ### 1) Hook import
1039
+
1040
+ \`\`\`ts
1041
+ import { useCreateUser } from "@intrig/react/users/client";
1042
+ \`\`\`
1043
+
1044
+ ### 2) Utility guards
1045
+
1046
+ \`\`\`ts
1047
+ import { isPending, isError, isSuccess } from "@intrig/react";
1048
+ \`\`\`
1049
+
1050
+ ### 3) Hook instance (auto-clear on unmount)
1051
+
1052
+ \`\`\`ts
1053
+ const [createUserResp, createUser] = useCreateUser({ clearOnUnmount: true });
1054
+ \`\`\`
1055
+
1056
+ Intrig stateful hooks expose a **NetworkState** plus **actions** to fetch / clear.
1057
+ Use this when you want a cached, reusable result tied to a global store.
1058
+
1059
+ ---
1060
+
1061
+ ## TL;DR (copy–paste)
1062
+
1063
+ \`\`\`tsx
1064
+ import { useCreateUser } from "@intrig/react/users/client";
1065
+ import { isPending, isError, isSuccess } from "@intrig/react";
1066
+ import { useEffect, useMemo } from "react";
1067
+
1068
+ export default function Example() {
1069
+ const [createUserResp, createUser] = useCreateUser({ clearOnUnmount: true });
1070
+
1071
+ useEffect(() => {
1072
+ createUser(createUserRequest, {});
1073
+ }, [createUser, createUserRequest]);
1074
+
1075
+ const createUserData = useMemo(
1076
+ () => (isSuccess(createUserResp) ? createUserResp.data : undefined),
1077
+ [createUserResp],
1078
+ );
1079
+
1080
+ if (isPending(createUserResp)) return <>Loading…</>;
1081
+ if (isError(createUserResp))
1082
+ return <>Error: {String(createUserResp.error)}</>;
1083
+ return <pre>{JSON.stringify(createUserData, null, 2)}</pre>;
1084
+ }
1085
+ \`\`\`
1086
+
1087
+ ### Optional types (if generated by your build)
1088
+
1089
+ \`\`\`ts
1090
+ import type { CreateUserRequest } from "@intrig/react/demo_api/components/schemas/CreateUserRequest";
1091
+ // Prefer the concrete response type:
1092
+ import type { CreateUserResponseBody } from "@intrig/react/users/CreateUser.response";
1093
+ \`\`\`
1094
+
1095
+ ---
1096
+
1097
+ ## Hook API
1098
+
1099
+ \`\`\`ts
1100
+ // Options are consistent across hooks.
1101
+ type UseHookOptions = {
1102
+ /** Execute once after mount with provided params/body (if required). */
1103
+ fetchOnMount?: boolean;
1104
+ /** Reset the state on unmount (recommended). */
1105
+ clearOnUnmount?: boolean;
1106
+ /** Distinguish multiple instances of the same hook. */
1107
+ key?: string;
1108
+ /** Initial path params for endpoints that require them. */
1109
+ params?: unknown;
1110
+ /** Initial request body (for POST/PUT/etc.). */
1111
+ body?: CreateUserRequest;
1112
+ };
1113
+
1114
+ // Prefer concrete types if your build emits them:
1115
+ // import type { CreateUserResponseBody } from '@intrig/react/users/CreateUser.response';
1116
+
1117
+ type CreateUserData = CreateUserResponseBody; // replace with CreateUserResponseBody if generated
1118
+ type CreateUserRequest = { params?: unknown; body?: CreateUserRequest };
1119
+
1120
+ // Signature (shape shown; concrete generics vary per generated hook)
1121
+ declare function useCreateUser(
1122
+ options?: UseHookOptions,
1123
+ ): [NetworkState<CreateUserData>, (req: CreateUserRequest) => void, () => void];
1124
+ \`\`\`
1125
+
1126
+ ### NetworkState & guards
1127
+
1128
+ \`\`\`ts
1129
+ import { isPending, isError, isSuccess } from "@intrig/react";
1130
+ \`\`\`
1131
+
1132
+ ---
1133
+
1134
+ ## Conceptual model (Stateful = single source of truth)
1135
+
1136
+ - Lives in a shared store keyed by \`key\` + request signature.
1137
+ - Best for **read** endpoints you want to **reuse** or keep **warm** (lists, details, search).
1138
+ - Lifecycle helpers:
1139
+ - \`fetchOnMount\` to kick off automatically.
1140
+ - \`clearOnUnmount\` to clean up (recommended default).
1141
+ - \`key\` to isolate multiple independent instances.
1142
+
1143
+ ---
1144
+
1145
+ ## Usage patterns
1146
+
1147
+ ### 1) Controlled (most explicit)
1148
+
1149
+ \`\`\`tsx
1150
+ const [createUserResp, createUser, clearCreateUser] = useCreateUser();
1151
+
1152
+ useEffect(() => {
1153
+ createUser(createUserRequest, {});
1154
+ return clearCreateUser; // optional cleanup
1155
+ }, [createUser, clearCreateUser]);
1156
+ \`\`\`
1157
+
1158
+ <details><summary>Description</summary>
1159
+ <p><strong>Use when</strong> you need explicit control over when a request fires, what params/body it uses, and when to clean up. Ideal for search forms, pagination, conditional fetches.</p>
1160
+ </details>
1161
+
1162
+ ### 2) Lifecycle-bound (shorthand)
1163
+
1164
+ \`\`\`tsx
1165
+ const [createUserResp] = useCreateUser({
1166
+ fetchOnMount: true,
1167
+ clearOnUnmount: true,
1168
+ body: createUserRequest,
1169
+ params: {},
1170
+ });
1171
+ \`\`\`
1172
+
1173
+ <details><summary>Description</summary>
1174
+ <p><strong>Use when</strong> the data should follow the component lifecycle: fetch once on mount, reset on unmount.</p>
1175
+ </details>
1176
+
1177
+ ### 3) Passive observer (render when data arrives)
1178
+
1179
+ \`\`\`tsx
1180
+ const [createUserResp] = useCreateUser();
1181
+ return isSuccess(createUserResp) ? <>{String(createUserResp.data)}</> : null;
1182
+ \`\`\`
1183
+
1184
+ <details><summary>Description</summary>
1185
+ <p><strong>Use when</strong> another part of the app triggers the fetch and this component only reads the state.</p>
1186
+ </details>
1187
+
1188
+ ### 4) Keep previous success while refetching (sticky)
1189
+
1190
+ \`\`\`tsx
1191
+ const [createUserData, setCreateUserData] = useState<any>();
1192
+ const [createUserResp, createUser] = useCreateUser();
1193
+
1194
+ useEffect(() => {
1195
+ if (isSuccess(createUserResp)) setCreateUserData(createUserResp.data);
1196
+ }, [createUserResp]);
1197
+
1198
+ return (
1199
+ <>
1200
+ {isPending(createUserResp) && createUserData ? (
1201
+ <div>Refreshing…</div>
1202
+ ) : null}
1203
+ <pre>
1204
+ {JSON.stringify(
1205
+ isSuccess(createUserResp) ? createUserResp.data : createUserData,
1206
+ null,
1207
+ 2,
1208
+ )}
1209
+ </pre>
1210
+ </>
1211
+ );
1212
+ \`\`\`
1213
+
1214
+ <details><summary>Description</summary>
1215
+ <p><strong>Use when</strong> you want SWR-like UX without flicker.</p>
1216
+ </details>
1217
+
1218
+ ### 5) Multiple instances of the same hook (isolate with \`key\`)
1219
+
1220
+ \`\`\`tsx
1221
+ const a = useCreateUser({ key: "A", fetchOnMount: true, params: {} });
1222
+ const b = useCreateUser({ key: "B", fetchOnMount: true, params: {} });
1223
+ \`\`\`
1224
+
1225
+ <details><summary>Description</summary>
1226
+ <p>Use unique keys to prevent state collisions.</p>
1227
+ </details>
1228
+
1229
+ ---
1230
+
1231
+ ## Before / After mini-migrations
1232
+
1233
+ ### If you mistakenly used Stateful for a simple submit → switch to Async
1234
+
1235
+ \`\`\`diff
1236
+ - const [createUserResp, createUser] = useCreateUser();
1237
+ - createUser(createUserRequest, {});
1238
+ + const [fn] = useCreateUserAsync();
1239
+ + await fn(createUserRequest);
1240
+ \`\`\`
1241
+
1242
+ ### If you started with Async but need to read later in another component → Stateful
1243
+
1244
+ \`\`\`diff
1245
+ - const [fn] = useCreateUserAsync();
1246
+ - const data = await fn(createUserRequest);
1247
+ + const [createUserResp, createUser] = useCreateUser({ fetchOnMount: true, clearOnUnmount: true, params: {}, body: createUserRequest });
1248
+ + // read from createUserResp anywhere with useCreateUser()
1249
+ \`\`\`
1250
+
1251
+ ---
1252
+
1253
+ ## Anti-patterns
1254
+
1255
+ <details><summary>Don’t use Stateful for field validations or a one-off submit</summary>
1256
+ Use the Async variant instead: \`const [fn] = useCreateUserAsync()\`.
1257
+ </details>
1258
+ <details><summary>Don’t use Async for long-lived lists or detail views</summary>
1259
+ Use Stateful so other components can read the same data and you can avoid refetch churn.
1260
+ </details>
1261
+ <details><summary>Don’t forget required \`params\` when using \`fetchOnMount\`</summary>
1262
+ Provide \`params\` (and \`body\` if applicable) or switch to the controlled pattern.
1263
+ </details>
1264
+ <details><summary>Rendering the same hook twice without a \`key\`</summary>
1265
+ If they should be independent, add a unique \`key\`.
1266
+ </details>
1267
+
1268
+ ---
1269
+
1270
+ ## Error & UX guidance
1271
+
1272
+ - **Loading:** early return or inline spinner. Prefer **sticky data** to avoid blanking content.
1273
+ - **Errors:** show a banner or inline errors depending on UX; keep previous good state if possible.
1274
+ - **Cleanup:** prefer \`clearOnUnmount: true\` as the default.
1275
+
1276
+ ---
1277
+
1278
+ ## Concurrency patterns
1279
+
1280
+ - **Refresh:** call the action again; combine with sticky data for smooth UX.
1281
+ - **Dedupe:** isolate instances with \`key\`.
1282
+ - **Parallel:** render two keyed instances; don’t share the same key.
1283
+
1284
+ ---
1285
+
1286
+ ## Full examples
1287
+
1288
+ ### Short format (lifecycle-bound)
1289
+
1290
+ \`\`\`tsx
1291
+ import { useCreateUser } from "@intrig/react/users/client";
1292
+ import { isPending, isError, isSuccess } from "@intrig/react";
1293
+ import { useMemo } from "react";
1294
+
1295
+ function ShortExample() {
1296
+ const [createUserResp] = useCreateUser({
1297
+ fetchOnMount: true,
1298
+ clearOnUnmount: true,
1299
+ body: createUserRequest,
1300
+ params: {},
1301
+ });
1302
+
1303
+ const createUserData = useMemo(
1304
+ () => (isSuccess(createUserResp) ? createUserResp.data : undefined),
1305
+ [createUserResp],
1306
+ );
1307
+
1308
+ if (isPending(createUserResp)) return <>Loading…</>;
1309
+ if (isError(createUserResp))
1310
+ return <>Error: {String(createUserResp.error)}</>;
1311
+ return <pre>{JSON.stringify(createUserData, null, 2)}</pre>;
1312
+ }
1313
+ \`\`\`
1314
+
1315
+ <details><summary>Description</summary>
1316
+ <p>Compact lifecycle-bound approach. Great for read-only pages that load once and clean up on unmount.</p>
1317
+ </details>
1318
+
1319
+ ### Controlled format (explicit actions)
1320
+
1321
+ \`\`\`tsx
1322
+ import { useCreateUser } from "@intrig/react/users/client";
1323
+ import { isPending, isError, isSuccess } from "@intrig/react";
1324
+ import { useEffect, useMemo } from "react";
1325
+
1326
+ function ControlledExample() {
1327
+ const [createUserResp, createUser, clearCreateUser] = useCreateUser();
1328
+
1329
+ useEffect(() => {
1330
+ createUser(createUserRequest, {});
1331
+ return clearCreateUser;
1332
+ }, [createUser, clearCreateUser]);
1333
+
1334
+ const createUserData = useMemo(
1335
+ () => (isSuccess(createUserResp) ? createUserResp.data : undefined),
1336
+ [createUserResp],
1337
+ );
1338
+
1339
+ if (isPending(createUserResp)) return <>Loading…</>;
1340
+ if (isError(createUserResp))
1341
+ return <>An error occurred: {String(createUserResp.error)}</>;
1342
+ return <pre>{JSON.stringify(createUserData, null, 2)}</pre>;
1343
+ }
1344
+ \`\`\`
1345
+
1346
+ ---
1347
+
1348
+ ## Gotchas & Tips
1349
+
1350
+ - Prefer **\`clearOnUnmount: true\`** in most components.
1351
+ - Use **\`key\`** for multiple independent instances.
1352
+ - Memoize derived values with **\`useMemo\`** to avoid churn.
1353
+ - Inline indicators keep the rest of the page interactive.
1354
+
1355
+ ---
1356
+
1357
+ ## Reference: State helpers
1358
+
1359
+ \`\`\`ts
1360
+ if (isPending(createUserResp)) {
1361
+ /* show spinner */
1362
+ }
1363
+ if (isError(createUserResp)) {
1364
+ /* show error */
1365
+ }
1366
+ if (isSuccess(createUserResp)) {
1367
+ /* read createUserResp.data */
1368
+ }
1369
+ \`\`\`
1370
+ "
1371
+ `;