ghcopilot-hub 1.0.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.
- package/README.md +176 -0
- package/hub/agents/README.md +243 -0
- package/hub/agents/archiver.agent.md +231 -0
- package/hub/agents/explore.agent.md +49 -0
- package/hub/agents/implementador.agent.md +176 -0
- package/hub/agents/librarian.agent.md +34 -0
- package/hub/agents/momus.agent.md +130 -0
- package/hub/agents/oracle.agent.md +52 -0
- package/hub/agents/plan-guardian.agent.md +109 -0
- package/hub/agents/planificador.agent.md +295 -0
- package/hub/agents/test-sentinel.agent.md +106 -0
- package/hub/base/.github/copilot-instructions.md +10 -0
- package/hub/base/.github/instructions/ghcopilot-hub.instructions.md +6 -0
- package/hub/base/.github/prompts/ghcopilot-hub-maintenance.prompt.md +8 -0
- package/hub/base/.vscode/settings.json +1 -0
- package/hub/packs/base-web.json +4 -0
- package/hub/packs/nextjs-ssr.json +4 -0
- package/hub/packs/node-api.json +4 -0
- package/hub/packs/spa-tanstack.json +4 -0
- package/hub/skills/architecture-testing/SKILL.md +108 -0
- package/hub/skills/architecture-testing/references/archunitts.md +46 -0
- package/hub/skills/ghcopilot-hub-consumer/SKILL.md +115 -0
- package/hub/skills/ghcopilot-hub-consumer/references/workflow.md +39 -0
- package/hub/skills/mermaid-expert/SKILL.md +152 -0
- package/hub/skills/mermaid-expert/assets/examples/c4_model.md +121 -0
- package/hub/skills/mermaid-expert/assets/examples/flowchart.md +123 -0
- package/hub/skills/mermaid-expert/assets/examples/img/base_minimal.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/corporate.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/dark.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/dark_neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/default_neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/forest_corp.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/handdrawn.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/neutral_sketch.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/retro.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/sequence.md +116 -0
- package/hub/skills/mermaid-expert/assets/examples/styles_and_looks.md +102 -0
- package/hub/skills/mermaid-expert/assets/examples/technical.md +130 -0
- package/hub/skills/mermaid-expert/assets/examples.md +57 -0
- package/hub/skills/mermaid-expert/references/cheatsheet.md +88 -0
- package/hub/skills/mermaid-expert/references/validation.md +66 -0
- package/hub/skills/react/SKILL.md +235 -0
- package/hub/skills/react/references/common-mistakes.md +518 -0
- package/hub/skills/react/references/composition-patterns.md +526 -0
- package/hub/skills/react/references/effects-patterns.md +396 -0
- package/hub/skills/react/references/react-compiler.md +268 -0
- package/hub/skills/react-hook-form/SKILL.md +291 -0
- package/hub/skills/react-hook-form/references/field-arrays.md +98 -0
- package/hub/skills/react-hook-form/references/integration.md +102 -0
- package/hub/skills/react-hook-form/references/performance.md +96 -0
- package/hub/skills/skill-creator/SKILL.md +152 -0
- package/hub/skills/skill-creator/assets/SKILL-TEMPLATE.md +84 -0
- package/hub/skills/skill-judge/README.md +261 -0
- package/hub/skills/skill-judge/SKILL.md +806 -0
- package/hub/skills/tailwind/SKILL.md +200 -0
- package/hub/skills/tanstack/SKILL.md +284 -0
- package/hub/skills/tanstack/references/loader-adapter-examples.md +79 -0
- package/hub/skills/tanstack/references/query-options-examples.md +115 -0
- package/hub/skills/tanstack/references/resilience-patterns.md +110 -0
- package/hub/skills/tanstack/references/suspense-consumption-examples.md +82 -0
- package/hub/skills/tanstack-query/SKILL.md +241 -0
- package/hub/skills/tanstack-query/references/advanced-hooks.md +126 -0
- package/hub/skills/tanstack-query/references/best-practices.md +241 -0
- package/hub/skills/tanstack-query/references/cache-strategies.md +474 -0
- package/hub/skills/tanstack-query/references/common-patterns.md +239 -0
- package/hub/skills/tanstack-query/references/migration-v5.md +93 -0
- package/hub/skills/tanstack-query/references/resilience-and-mutations.md +63 -0
- package/hub/skills/tanstack-query/references/testing.md +116 -0
- package/hub/skills/tanstack-query/references/top-errors.md +148 -0
- package/hub/skills/tanstack-query/references/typescript.md +176 -0
- package/hub/skills/tanstack-router/SKILL.md +145 -0
- package/hub/skills/tanstack-router/references/code-splitting.md +31 -0
- package/hub/skills/tanstack-router/references/errors-and-boundaries.md +44 -0
- package/hub/skills/tanstack-router/references/loaders-and-preload.md +51 -0
- package/hub/skills/tanstack-router/references/navigation.md +24 -0
- package/hub/skills/tanstack-router/references/private-routes.md +169 -0
- package/hub/skills/tanstack-router/references/router-context.md +35 -0
- package/hub/skills/tanstack-router/references/search-params.md +29 -0
- package/hub/skills/tanstack-router/references/typescript.md +24 -0
- package/hub/skills/testing/SKILL.md +187 -0
- package/hub/skills/testing/references/assertions.md +64 -0
- package/hub/skills/testing/references/async-testing.md +66 -0
- package/hub/skills/testing/references/e2e-strategy.md +69 -0
- package/hub/skills/testing/references/layer-matrix.md +67 -0
- package/hub/skills/testing/references/performance.md +49 -0
- package/hub/skills/testing/references/tooling-map.md +81 -0
- package/hub/skills/testing/references/zustand-mocking.md +84 -0
- package/hub/skills/typescript/SKILL.md +232 -0
- package/hub/skills/typescript/references/perf-additional-concerns.md +248 -0
- package/hub/skills/typescript/references/perf-execution-cache-locality.md +178 -0
- package/hub/skills/typescript/references/reduce-branching.md +147 -0
- package/hub/skills/typescript/references/reduce-looping.md +203 -0
- package/hub/skills/typescript/references/style-and-types.md +171 -0
- package/hub/skills/typescript/references/type-vs-interface.md +27 -0
- package/hub/skills/zod/SKILL.md +219 -0
- package/hub/skills/zustand/SKILL.md +273 -0
- package/package.json +59 -0
- package/tooling/cli/src/bin.js +11 -0
- package/tooling/cli/src/cli.js +409 -0
- package/tooling/cli/src/lib/catalog-loader.js +191 -0
- package/tooling/cli/src/lib/constants.js +39 -0
- package/tooling/cli/src/lib/errors.js +8 -0
- package/tooling/cli/src/lib/frontmatter.js +41 -0
- package/tooling/cli/src/lib/fs-utils.js +77 -0
- package/tooling/cli/src/lib/managed-header.js +74 -0
- package/tooling/cli/src/lib/manifest.js +105 -0
- package/tooling/cli/src/lib/resolver.js +53 -0
- package/tooling/cli/src/lib/sync-engine.js +262 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-hook-form
|
|
3
|
+
description: >
|
|
4
|
+
High-performance React Hook Form patterns for Clean Architecture applications, including subscription isolation,
|
|
5
|
+
controlled-component wiring, field-array safety, async defaults, and Zod boundary design. Use this skill when
|
|
6
|
+
implementing or reviewing useForm, useWatch, useController, useFieldArray, resolver/defaultValues, or when
|
|
7
|
+
debugging form re-render/performance issues in React + TypeScript projects.
|
|
8
|
+
license: Apache-2.0
|
|
9
|
+
metadata:
|
|
10
|
+
author: jmgomezdev
|
|
11
|
+
version: "1.0"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
Use this skill when you need to:
|
|
17
|
+
|
|
18
|
+
- Create or refactor form hooks in `application/{feature}/hooks/forms/`.
|
|
19
|
+
- Integrate RHF with controlled UI libraries (Shadcn/Radix, MUI, AntD).
|
|
20
|
+
- Diagnose re-render storms, laggy typing, or unstable form state.
|
|
21
|
+
- Implement dynamic arrays with `useFieldArray` safely.
|
|
22
|
+
- Decide where validation rules belong between Domain/Application/Presentation.
|
|
23
|
+
|
|
24
|
+
## Progressive Loading Strategy
|
|
25
|
+
|
|
26
|
+
Read only what is needed for the current task.
|
|
27
|
+
|
|
28
|
+
| Scenario | Action |
|
|
29
|
+
| ----------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
30
|
+
| Re-renders, lag, subscription issues (`watch`, `useWatch`, `formState`) | **MANDATORY**: Read `references/performance.md` fully. |
|
|
31
|
+
| UI library wiring issues (`Controller`, `useController`, Shadcn/MUI) | **MANDATORY**: Read `references/integration.md` fully. |
|
|
32
|
+
| Dynamic rows/lists (`useFieldArray`, append/remove/reorder bugs) | **MANDATORY**: Read `references/field-arrays.md` fully. |
|
|
33
|
+
| General form setup with no special issue | Stay in this file only. |
|
|
34
|
+
|
|
35
|
+
Do NOT load reference files that are unrelated to the active scenario.
|
|
36
|
+
|
|
37
|
+
## Clean Architecture Boundaries
|
|
38
|
+
|
|
39
|
+
- Domain: base Zod schemas and business invariants only. No UI-only constraints.
|
|
40
|
+
- Application: form hooks (`useForm*`) and mapping to defaults.
|
|
41
|
+
- Presentation: rendering, input components, visual feedback, submit UX.
|
|
42
|
+
|
|
43
|
+
Boundary rule:
|
|
44
|
+
|
|
45
|
+
- Presentation must not import DTOs or repositories.
|
|
46
|
+
- Form-specific UI rules that do not belong to enterprise business rules live in Application.
|
|
47
|
+
|
|
48
|
+
## Expert Mindset
|
|
49
|
+
|
|
50
|
+
Before writing form code, ask:
|
|
51
|
+
|
|
52
|
+
- Subscription scope: "Who truly needs this value, and what is the smallest component that can subscribe?"
|
|
53
|
+
- Validation cost: "Can this rule run on submit/blur instead of every keystroke?"
|
|
54
|
+
- State ownership: "Is this server shape, domain shape, or UI-transient shape?"
|
|
55
|
+
- Mutation safety: "Will this array/input operation preserve RHF internal identity and state?"
|
|
56
|
+
|
|
57
|
+
These questions prevent the most common performance and consistency regressions.
|
|
58
|
+
|
|
59
|
+
## Critical Patterns
|
|
60
|
+
|
|
61
|
+
### 1) Stable `useForm` Baseline
|
|
62
|
+
|
|
63
|
+
Always provide explicit `defaultValues` and explicit validation timing.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
export function useProfileForm(): UseFormReturn<ProfileForm> {
|
|
67
|
+
return useForm<ProfileForm>({
|
|
68
|
+
resolver: zodResolver(profileSchema),
|
|
69
|
+
defaultValues: getInitialProfile(),
|
|
70
|
+
mode: "onSubmit",
|
|
71
|
+
reValidateMode: "onBlur",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Why this matters:
|
|
77
|
+
|
|
78
|
+
- Missing defaults causes uncontrolled/controlled drift and unreliable `reset` behavior.
|
|
79
|
+
- Aggressive modes (`onChange`) can inflate re-render and validation cost.
|
|
80
|
+
|
|
81
|
+
### 2) Isolate Subscriptions with `useWatch`
|
|
82
|
+
|
|
83
|
+
Watch as deep as possible, never broadly at the form root unless you intentionally need full-form updates.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
function OrderForm() {
|
|
87
|
+
const { control, register, handleSubmit } = useOrderForm();
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<form onSubmit={handleSubmit(console.log)}>
|
|
91
|
+
<input {...register("customerName")} />
|
|
92
|
+
<ShippingPreview control={control} />
|
|
93
|
+
</form>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function ShippingPreview({ control }: { control: Control<OrderFormData> }) {
|
|
98
|
+
const method = useWatch({ control, name: "shippingMethod" });
|
|
99
|
+
return <p>Method: {method}</p>;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3) Isolate Controlled Inputs with `useController`
|
|
104
|
+
|
|
105
|
+
Wrap each controlled input in a leaf component so one input update does not re-render large parents.
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
function ControlledCurrencyInput({
|
|
109
|
+
control,
|
|
110
|
+
name,
|
|
111
|
+
}: {
|
|
112
|
+
control: Control<InvoiceForm>;
|
|
113
|
+
name: "amount";
|
|
114
|
+
}) {
|
|
115
|
+
const { field, fieldState } = useController({ control, name });
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<Input
|
|
120
|
+
value={field.value}
|
|
121
|
+
onBlur={field.onBlur}
|
|
122
|
+
onChange={(e) => field.onChange(Number(e.target.value || 0))}
|
|
123
|
+
ref={field.ref}
|
|
124
|
+
/>
|
|
125
|
+
{fieldState.error?.message && <span>{fieldState.error.message}</span>}
|
|
126
|
+
</>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 4) Keep Schemas and Resolvers Stable
|
|
132
|
+
|
|
133
|
+
Define schemas outside render scope. Never recreate schema/resolver per render.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
export const loginSchema = z.object({
|
|
137
|
+
email: z.email(),
|
|
138
|
+
password: z.string().min(1, "Required"),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export function useLoginForm(): UseFormReturn<LoginForm> {
|
|
142
|
+
return useForm<LoginForm>({
|
|
143
|
+
resolver: zodResolver(loginSchema),
|
|
144
|
+
defaultValues: getInitialLogin(),
|
|
145
|
+
mode: "onSubmit",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 5) Use Async `defaultValues` for One-shot Hydration
|
|
151
|
+
|
|
152
|
+
When initial values depend on remote data, prefer async `defaultValues` over `useEffect + reset`.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
export function useProfileEditForm(userId: string): UseFormReturn<ProfileForm> {
|
|
156
|
+
return useForm<ProfileForm>({
|
|
157
|
+
resolver: zodResolver(profileSchema),
|
|
158
|
+
defaultValues: async () => {
|
|
159
|
+
const user = await getUserProfile(userId);
|
|
160
|
+
return getInitialProfile(user);
|
|
161
|
+
},
|
|
162
|
+
mode: "onSubmit",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 6) Field Array Identity Safety
|
|
168
|
+
|
|
169
|
+
Use `field.id` as key and append complete objects only.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
const { fields, append, remove } = useFieldArray({ control, name: "items" });
|
|
173
|
+
|
|
174
|
+
{
|
|
175
|
+
fields.map((field, index) => (
|
|
176
|
+
<div key={field.id}>
|
|
177
|
+
<input {...register(`items.${index}.sku`)} />
|
|
178
|
+
<button type="button" onClick={() => remove(index)}>
|
|
179
|
+
Remove
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
<button type="button" onClick={() => append(getInitialCartItem())}>
|
|
186
|
+
Add
|
|
187
|
+
</button>;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Never Do This
|
|
191
|
+
|
|
192
|
+
- Never call `watch()` without args in the root form by default. Reason: it subscribes the root to everything and
|
|
193
|
+
amplifies re-renders.
|
|
194
|
+
|
|
195
|
+
- Never mix `register()` and `useController` for the same field. Reason: duplicate registration creates conflicting
|
|
196
|
+
state ownership.
|
|
197
|
+
|
|
198
|
+
- Never build Zod schemas inside components. Reason: resolver cache is invalidated and validation cost spikes.
|
|
199
|
+
|
|
200
|
+
- Never use array index as React key in `useFieldArray` lists. Reason: reorder/remove operations can corrupt
|
|
201
|
+
item-field association.
|
|
202
|
+
|
|
203
|
+
- Never put DTO/API contracts in Presentation forms. Reason: it leaks infrastructure concerns and breaks layering
|
|
204
|
+
boundaries.
|
|
205
|
+
|
|
206
|
+
- Never enable `shouldUnregister: true` by default in wizard-like forms. Reason: step transitions unmount fields and
|
|
207
|
+
silently drop values the user expects to persist.
|
|
208
|
+
|
|
209
|
+
- Never chain array mutations like `append()` and `remove()` in the same synchronous handler. Reason: RHF internal
|
|
210
|
+
item identity can shift mid-tick, producing hard-to-reproduce row/value mismatch.
|
|
211
|
+
|
|
212
|
+
## Decision Matrix
|
|
213
|
+
|
|
214
|
+
| Problem | First Check | Fallback |
|
|
215
|
+
| -------------------------- | ------------------------------------------------------ | ---------------------------------------------------------- |
|
|
216
|
+
| Typing lag in big form | Root-level `watch()` or broad `formState` subscription | Move watchers to leaf + use `useFormState`/`getFieldState` |
|
|
217
|
+
| Controlled UI not updating | `onChange`/`value` mapping in `useController` | Wrap library component in dedicated adapter |
|
|
218
|
+
| Form hydration race | `useEffect + reset` pattern | Replace with async `defaultValues` |
|
|
219
|
+
| Array rows losing values | `key={index}` or partial `append` object | Switch to `field.id` and complete defaults |
|
|
220
|
+
|
|
221
|
+
## Common Failure Modes
|
|
222
|
+
|
|
223
|
+
| Symptom | Likely Cause | Recommended Fix |
|
|
224
|
+
| --------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
225
|
+
| First change is missed in dependent UI | `useWatch` subscription established after an early `setValue` | Pair `useWatch` with `getValues` for initial read, then subscribe |
|
|
226
|
+
| Wizard step values disappear | `shouldUnregister: true` on multi-step forms | Keep `shouldUnregister` disabled for wizard flows |
|
|
227
|
+
| Numeric field flips to `NaN` | Empty string passed through `valueAsNumber` | Normalize empty input before `onChange` coercion |
|
|
228
|
+
| Switching entity ID does not refresh defaults | Assuming async `defaultValues` re-runs automatically | Trigger explicit `reset(nextDefaults)` when entity identity changes |
|
|
229
|
+
|
|
230
|
+
## Minimal End-to-End Pattern
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// Domain
|
|
234
|
+
export const createProductSchema = z.object({
|
|
235
|
+
name: z.string().min(1),
|
|
236
|
+
price: z.number().nonnegative(),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export function getInitialCreateProduct(): z.infer<typeof createProductSchema> {
|
|
240
|
+
return { name: "", price: 0 };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Application
|
|
244
|
+
export function useCreateProductForm(): UseFormReturn<CreateProductForm> {
|
|
245
|
+
return useForm<CreateProductForm>({
|
|
246
|
+
resolver: zodResolver(createProductSchema),
|
|
247
|
+
defaultValues: getInitialCreateProduct(),
|
|
248
|
+
mode: "onSubmit",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
// Presentation
|
|
255
|
+
export function CreateProductPage() {
|
|
256
|
+
const {
|
|
257
|
+
register,
|
|
258
|
+
handleSubmit,
|
|
259
|
+
formState: { errors, isSubmitting },
|
|
260
|
+
} = useCreateProductForm();
|
|
261
|
+
|
|
262
|
+
const onSubmit: SubmitHandler<CreateProductForm> = async (data) => {
|
|
263
|
+
await createProduct(data);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
268
|
+
<input {...register("name")} />
|
|
269
|
+
{errors.name?.message}
|
|
270
|
+
<input type="number" {...register("price", { valueAsNumber: true })} />
|
|
271
|
+
{errors.price?.message}
|
|
272
|
+
<button disabled={isSubmitting}>Save</button>
|
|
273
|
+
</form>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Commands
|
|
279
|
+
|
|
280
|
+
Use VS Code native search (`#tool:search`) with these patterns:
|
|
281
|
+
|
|
282
|
+
- Dangerous root subscriptions: `query: watch\(\)` (regex), path: `src`
|
|
283
|
+
- Index keys in field arrays: `query: key=\{index\}` (regex), path: `src`
|
|
284
|
+
- Broad `formState` usage in hot components: `query: formState`, path: `src`
|
|
285
|
+
|
|
286
|
+
## Resources
|
|
287
|
+
|
|
288
|
+
- Performance and subscriptions: `references/performance.md`
|
|
289
|
+
- UI-library integration: `references/integration.md`
|
|
290
|
+
- Dynamic arrays: `references/field-arrays.md`
|
|
291
|
+
- Zod schema guidance: use the `zod` skill
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Field Arrays Reference (`useFieldArray`)
|
|
2
|
+
|
|
3
|
+
Load this file when implementing dynamic rows/lists, reorder actions, or debugging value loss in array items.
|
|
4
|
+
|
|
5
|
+
## Invariants
|
|
6
|
+
|
|
7
|
+
- Use `field.id` as React key. Never use index.
|
|
8
|
+
- Append/prepend/insert complete objects, not partials.
|
|
9
|
+
- Keep one `useFieldArray` instance per `name`.
|
|
10
|
+
- Keep operations separated when state order can change.
|
|
11
|
+
|
|
12
|
+
These invariants protect RHF's internal item identity.
|
|
13
|
+
|
|
14
|
+
## Correct Baseline
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
const { control, register } = useOrderForm();
|
|
18
|
+
const { fields, append, remove, move } = useFieldArray({
|
|
19
|
+
control,
|
|
20
|
+
name: "items",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
{fields.map((field, index) => (
|
|
26
|
+
<div key={field.id}>
|
|
27
|
+
<input {...register(`items.${index}.sku`)} />
|
|
28
|
+
<input type="number" {...register(`items.${index}.qty`, { valueAsNumber: true })} />
|
|
29
|
+
<button type="button" onClick={() => remove(index)}>
|
|
30
|
+
Remove
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
|
|
35
|
+
<button type="button" onClick={() => append(getInitialOrderItem())}>
|
|
36
|
+
Add item
|
|
37
|
+
</button>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Operation Semantics
|
|
43
|
+
|
|
44
|
+
| Operation | Safe Usage | Risk |
|
|
45
|
+
| ------------------------------- | ------------------------------ | ------------------------------------------------------------------ |
|
|
46
|
+
| `append` / `prepend` / `insert` | Provide full item defaults | Partial objects break validation/state |
|
|
47
|
+
| `remove` | Trigger from isolated action | Chaining with append in same tick can create confusing transitions |
|
|
48
|
+
| `move` / `swap` | Keep stable `field.id` keys | Index keys corrupt mapping after reorder |
|
|
49
|
+
| `replace` | Prefer for full server re-sync | Can reset dirty/touched assumptions |
|
|
50
|
+
|
|
51
|
+
## Common Failure Modes
|
|
52
|
+
|
|
53
|
+
| Symptom | Root Cause | Fix |
|
|
54
|
+
| ---------------------------------------- | -------------------------------------------- | --------------------------------------------------------- |
|
|
55
|
+
| Values jump to another row after reorder | `key={index}` | Use `key={field.id}` |
|
|
56
|
+
| New rows fail validation immediately | Missing required properties in appended item | Use `getInitial*` full object |
|
|
57
|
+
| Nested arrays become unstable | Multiple hooks controlling same path | One `useFieldArray` per path and isolate child components |
|
|
58
|
+
| Row state lost with virtualization | Rows unmount without persistent form context | Keep `FormProvider` context and verify mount strategy |
|
|
59
|
+
|
|
60
|
+
## Nested Arrays Guidance
|
|
61
|
+
|
|
62
|
+
For nested paths, keep names explicit and typed where possible.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
const childArray = useFieldArray({
|
|
66
|
+
control,
|
|
67
|
+
name: `sections.${sectionIndex}.items` as const,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use child row components so each level subscribes only to the path it renders.
|
|
72
|
+
|
|
73
|
+
## Never Do This
|
|
74
|
+
|
|
75
|
+
- Never key rows by index.
|
|
76
|
+
Reason: after reorder/remove, React reuses DOM nodes for different RHF items and values appear to jump rows.
|
|
77
|
+
|
|
78
|
+
- Never append partial items.
|
|
79
|
+
Reason: missing properties create mixed default/dirty state and validation behavior diverges between old and new rows.
|
|
80
|
+
Recovery: append only `getInitial*()` full objects.
|
|
81
|
+
|
|
82
|
+
- Never share one array field name across multiple independent components.
|
|
83
|
+
Reason: multiple controllers mutate the same path, causing race-like updates and unstable field registration order.
|
|
84
|
+
|
|
85
|
+
- Never execute `append` and `remove` for the same array in a single synchronous click handler.
|
|
86
|
+
Reason: identity recalculation can happen between operations and produce intermittent row/state mismatch.
|
|
87
|
+
Recovery: split into separate user actions or defer second mutation.
|
|
88
|
+
|
|
89
|
+
## Useful Commands
|
|
90
|
+
|
|
91
|
+
Use VS Code native search (`#tool:search`) with these patterns:
|
|
92
|
+
|
|
93
|
+
- Risky index keys:
|
|
94
|
+
`query: key=\{index\}` (regex), path: `src`
|
|
95
|
+
- `useFieldArray` usage:
|
|
96
|
+
`query: useFieldArray`, path: `src`
|
|
97
|
+
- `append(` calls that may use partials:
|
|
98
|
+
`query: append\(` (regex), path: `src`
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Integration Reference (UI Libraries)
|
|
2
|
+
|
|
3
|
+
Load this file when wiring RHF with controlled or headless UI components (Shadcn/Radix, MUI, AntD, custom inputs).
|
|
4
|
+
|
|
5
|
+
## Integration Mindset
|
|
6
|
+
|
|
7
|
+
Treat every third-party input as an adapter problem:
|
|
8
|
+
|
|
9
|
+
- Application owns `useForm` and schema wiring.
|
|
10
|
+
- Presentation owns the component adapter.
|
|
11
|
+
- Adapter maps library event/value contract <-> RHF `field` contract.
|
|
12
|
+
|
|
13
|
+
If mapping is ambiguous, create a dedicated wrapper component instead of inlining custom wiring repeatedly.
|
|
14
|
+
|
|
15
|
+
## Adapter Contract Checklist
|
|
16
|
+
|
|
17
|
+
For any controlled component, verify all 4 mappings:
|
|
18
|
+
|
|
19
|
+
- `value`
|
|
20
|
+
- `onChange`
|
|
21
|
+
- `onBlur`
|
|
22
|
+
- `ref`
|
|
23
|
+
|
|
24
|
+
Missing one usually causes dirty/touched/validation inconsistencies.
|
|
25
|
+
|
|
26
|
+
## Shadcn / Radix Specifics
|
|
27
|
+
|
|
28
|
+
Key point: many controls expose `onValueChange`, not native `onChange`.
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
<FormField
|
|
32
|
+
control={control}
|
|
33
|
+
name="role"
|
|
34
|
+
render={({ field }) => (
|
|
35
|
+
<Select value={field.value} onValueChange={field.onChange}>
|
|
36
|
+
<SelectTrigger onBlur={field.onBlur} ref={field.ref} />
|
|
37
|
+
<SelectContent>{/* options */}</SelectContent>
|
|
38
|
+
</Select>
|
|
39
|
+
)}
|
|
40
|
+
/>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Notes:
|
|
44
|
+
|
|
45
|
+
- Do not spread `field` blindly into components that are not input-compatible.
|
|
46
|
+
- Ensure the imported `Form` components are from your UI layer, not a conflicting package.
|
|
47
|
+
|
|
48
|
+
## MUI Specifics
|
|
49
|
+
|
|
50
|
+
MUI components can emit either event objects or direct values depending on component/version.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Controller
|
|
54
|
+
control={control}
|
|
55
|
+
name="amount"
|
|
56
|
+
render={({ field, fieldState }) => (
|
|
57
|
+
<TextField
|
|
58
|
+
value={field.value}
|
|
59
|
+
onChange={(e) => field.onChange(Number(e.target.value || 0))}
|
|
60
|
+
onBlur={field.onBlur}
|
|
61
|
+
inputRef={field.ref}
|
|
62
|
+
error={!!fieldState.error}
|
|
63
|
+
helperText={fieldState.error?.message}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Always normalize before calling `field.onChange` when the domain type is not string.
|
|
70
|
+
|
|
71
|
+
## Anti-Patterns
|
|
72
|
+
|
|
73
|
+
- Never double-register a field (`register` + `Controller/useController`).
|
|
74
|
+
Reason: creates competing owners for the same value/touched/dirty lifecycle and leads to non-deterministic updates.
|
|
75
|
+
|
|
76
|
+
- Never spread conversion/parsing logic across many pages.
|
|
77
|
+
Reason: numeric/date coercion drifts over time and creates inconsistent validation behavior per screen.
|
|
78
|
+
Recovery: move coercion into one reusable adapter component per input family.
|
|
79
|
+
|
|
80
|
+
- Never hide library-specific event contracts behind ambiguous wrappers.
|
|
81
|
+
Reason: maintainers assume native DOM `onChange`, then break touched/blur semantics when refactoring.
|
|
82
|
+
|
|
83
|
+
- Never omit `onBlur`/`ref` mapping when wiring controlled components.
|
|
84
|
+
Reason: field may appear to work while touched tracking, focus management, and validation timing become incorrect.
|
|
85
|
+
|
|
86
|
+
## Verification Checklist
|
|
87
|
+
|
|
88
|
+
Prioritize behavioral checks over snapshots:
|
|
89
|
+
|
|
90
|
+
- Changing value updates RHF state.
|
|
91
|
+
- Blur marks field as touched.
|
|
92
|
+
- Validation messages appear/disappear correctly.
|
|
93
|
+
- Numeric/date parsing is stable for empty and invalid values.
|
|
94
|
+
|
|
95
|
+
## Useful Commands
|
|
96
|
+
|
|
97
|
+
Use VS Code native search (`#tool:search`) with these patterns:
|
|
98
|
+
|
|
99
|
+
- Controlled wrappers and `Controller` usage:
|
|
100
|
+
`query: useController|<Controller` (regex), path: `src`
|
|
101
|
+
- Possible double registration smells:
|
|
102
|
+
`query: register\(|name=\"` (regex), path: `src/presentation`
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Performance Reference (RHF)
|
|
2
|
+
|
|
3
|
+
Load this file when the issue is about lag, excessive re-renders, unstable subscriptions, or expensive validation cycles.
|
|
4
|
+
|
|
5
|
+
## Quick Diagnosis Flow
|
|
6
|
+
|
|
7
|
+
1. Confirm where subscriptions happen.
|
|
8
|
+
|
|
9
|
+
- Search root form for `watch()` and broad `formState` usage.
|
|
10
|
+
- If parent subscribes, all children pay the render cost.
|
|
11
|
+
|
|
12
|
+
2. Confirm validation timing.
|
|
13
|
+
|
|
14
|
+
- `mode: 'onChange'` on large forms often creates avoidable work.
|
|
15
|
+
- Prefer `mode: 'onSubmit'` and `reValidateMode: 'onBlur'` unless UX requires immediate feedback.
|
|
16
|
+
|
|
17
|
+
3. Confirm hydration strategy.
|
|
18
|
+
|
|
19
|
+
- If code uses `useEffect + reset` for initial fetch, replace with async `defaultValues`.
|
|
20
|
+
- If entity identity changes later (edit A -> edit B), explicit `reset(nextDefaults)` is still required.
|
|
21
|
+
|
|
22
|
+
## Subscription Rules That Matter
|
|
23
|
+
|
|
24
|
+
- Use `useWatch` at the leaf where the value is consumed.
|
|
25
|
+
- Use `getValues` for one-time reads in event handlers or effects.
|
|
26
|
+
- Use `getFieldState(name)` for a single-field state check.
|
|
27
|
+
- Use `useFormState` in child components when only a subset of form state is needed.
|
|
28
|
+
|
|
29
|
+
Why:
|
|
30
|
+
|
|
31
|
+
- `watch()` at root subscribes the entire form.
|
|
32
|
+
- Reading full `formState` in a hot parent broadens subscriptions and increases render fan-out.
|
|
33
|
+
|
|
34
|
+
## `formState` Access Pattern
|
|
35
|
+
|
|
36
|
+
Bad:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
const { formState } = useFormContext<MyForm>();
|
|
40
|
+
return <SaveButton disabled={!formState.isValid || formState.isSubmitting} />;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Better:
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
const {
|
|
47
|
+
formState: { isSubmitting },
|
|
48
|
+
} = useFormContext<MyForm>();
|
|
49
|
+
return <SaveButton disabled={isSubmitting} />;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For single-field checks:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
const { getFieldState } = useFormContext<MyForm>();
|
|
56
|
+
const emailState = getFieldState("email");
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## `shouldUnregister` Decision
|
|
60
|
+
|
|
61
|
+
| Scenario | Use `shouldUnregister` | Why |
|
|
62
|
+
| ------------------------------------------------------ | ---------------------- | -------------------------------------------- |
|
|
63
|
+
| Frequently mounted/unmounted conditional fragments | Yes (case by case) | Can reduce retained state footprint |
|
|
64
|
+
| Multi-step wizards where hidden step data must persist | No | Unmounted fields lose values |
|
|
65
|
+
| Large tables with virtualization | Usually no | Rows unmount often; unregister may drop data |
|
|
66
|
+
|
|
67
|
+
## Common Failure Modes
|
|
68
|
+
|
|
69
|
+
| Symptom | Root Cause | Fix |
|
|
70
|
+
| -------------------------------------------------- | -------------------------------------------- | --------------------------------------------------- |
|
|
71
|
+
| Typing in one field re-renders unrelated sections | Root `watch()` or broad parent subscriptions | Move to leaf `useWatch`; split child components |
|
|
72
|
+
| First dependent render misses latest value | Subscription created after `setValue` | Combine `useWatch` with initial `getValues` read |
|
|
73
|
+
| Form flips uncontrolled/controlled | Missing `defaultValues` keys | Provide complete defaults for all mounted fields |
|
|
74
|
+
| Async edit form loads stale values after id change | Async defaults run once per mount | Call `reset(getInitial(entity))` on identity change |
|
|
75
|
+
|
|
76
|
+
## Never Do This
|
|
77
|
+
|
|
78
|
+
- Never put `methods` (whole `useForm` object) in a dependency array.
|
|
79
|
+
Reason: object identity is unstable and can trigger loops.
|
|
80
|
+
|
|
81
|
+
- Never optimize by `memo` first while root subscriptions remain broad.
|
|
82
|
+
Reason: subscription topology dominates; memo cannot fix wrong observer placement.
|
|
83
|
+
|
|
84
|
+
- Never disable resolver/schema stability by rebuilding schema in render.
|
|
85
|
+
Reason: breaks caching and increases validation churn.
|
|
86
|
+
|
|
87
|
+
## Useful Commands
|
|
88
|
+
|
|
89
|
+
Use VS Code native search (`#tool:search`) with these patterns:
|
|
90
|
+
|
|
91
|
+
- Root `watch()` usage (often a hotspot):
|
|
92
|
+
`query: watch\(\)` (regex), path: `src`
|
|
93
|
+
- Potential broad `formState` reads:
|
|
94
|
+
`query: formState`, path: `src`
|
|
95
|
+
- `onChange` validation mode in large forms:
|
|
96
|
+
`query: mode: 'onChange'`, path: `src`
|