@primereact/mcp 11.0.0 → 11.1.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/data/llms/headless/components/sidebar/api.json +1029 -54
- package/data/llms/headless/guides/misc/internationalization.md +282 -0
- package/data/llms/headless/guides/misc/locale.md +280 -0
- package/data/llms/llms-full.txt +9822 -1377
- package/data/llms/llms.txt +15 -0
- package/data/llms/primitive/components/sidebar/api.json +127 -33
- package/data/llms/primitive/guides/migration/updating-to-v11.md +2219 -0
- package/data/llms/primitive/guides/misc/internationalization.md +287 -0
- package/data/llms/primitive/guides/misc/locale.md +280 -0
- package/data/llms/styled/add-ons/designer/ci.md +273 -0
- package/data/llms/styled/add-ons/designer/guide.md +99 -0
- package/data/llms/styled/add-ons/designer/overview.md +194 -0
- package/data/llms/styled/add-ons/uikit/guide/v3.md +181 -0
- package/data/llms/styled/add-ons/uikit/guide/v4.md +163 -0
- package/data/llms/styled/add-ons/uikit/overview.md +204 -0
- package/data/llms/styled/components/datatable.md +8 -5
- package/data/llms/styled/components/menu.md +39 -41
- package/data/llms/styled/guides/configuration.md +198 -0
- package/data/llms/styled/guides/form/formik.md +448 -0
- package/data/llms/styled/guides/form/react-hook-form.md +503 -0
- package/data/llms/styled/guides/form/tanstack.md +502 -0
- package/data/llms/styled/guides/migration/updating-to-v11.md +2219 -0
- package/data/llms/styled/guides/misc/internationalization.md +379 -0
- package/data/llms/styled/guides/misc/locale.md +280 -0
- package/data/llms/tailwind/components/menu.md +5 -5
- package/data/llms/tailwind/components/tooltip.md +4 -13
- package/data/llms/tailwind/guides/misc/internationalization.md +287 -0
- package/data/llms/tailwind/guides/misc/locale.md +280 -0
- package/data/manifest.json +339 -24
- package/data/mcp-data.json +263 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# React Hook Form
|
|
2
|
+
|
|
3
|
+
Build validated forms with PrimeReact and React Hook Form.
|
|
4
|
+
|
|
5
|
+
<DocMessage>
|
|
6
|
+
Open the <a href="https://stackblitz.com/edit/vitejs-vite-jkwqpcwm?file=src%2Fcomponents%2Frhf-form.tsx" target="_blank" rel="noopener noreferrer">complete example on StackBlitz</a> to run and edit the form built in this guide.
|
|
7
|
+
</DocMessage>
|
|
8
|
+
|
|
9
|
+
## Introduction
|
|
10
|
+
|
|
11
|
+
[React Hook Form](https://react-hook-form.com/) manages form state, submission and validation with minimal re-renders. PrimeReact inputs are controlled, so they connect to the form through its `Controller` component.
|
|
12
|
+
|
|
13
|
+
Throughout this guide validation is handled with [Zod](https://zod.dev/): you describe the form once as a schema and React Hook Form enforces it on every change and submit.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react-hook-form zod @hookform/resolvers
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Anatomy
|
|
20
|
+
|
|
21
|
+
A field is a `Controller` wrapped around an input. The `Controller` owns the value in the form state and hands it to the input through its `render` callback:
|
|
22
|
+
|
|
23
|
+
```tsx title="Anatomy" showLineNumbers
|
|
24
|
+
<Controller
|
|
25
|
+
name="projectName" // path of this field in the form data
|
|
26
|
+
control={control} // the instance returned by useForm
|
|
27
|
+
render={({ field, fieldState }) => (
|
|
28
|
+
<InputText
|
|
29
|
+
{...field} // value, onChange, onBlur, name, ref
|
|
30
|
+
invalid={fieldState.invalid} // error styling
|
|
31
|
+
/>
|
|
32
|
+
)}
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **`field`**: the value and handlers that bind the input to the form (`value`, `onChange`, `onBlur`, `name`, `ref`).
|
|
37
|
+
- **`fieldState`**: the validation state of this field (`error`, `invalid`, `isTouched`, `isDirty`).
|
|
38
|
+
|
|
39
|
+
Spreading `{...field}` is enough for native inputs like `InputText` and `Textarea`. Other components need their change forwarded explicitly, which is covered in [Fields](#fields).
|
|
40
|
+
|
|
41
|
+
## Create the form
|
|
42
|
+
|
|
43
|
+
### Schema
|
|
44
|
+
|
|
45
|
+
Describe the shape and rules of the form with Zod. The message passed to each rule is what surfaces as the field error:
|
|
46
|
+
|
|
47
|
+
```tsx title="schema.ts" showLineNumbers
|
|
48
|
+
import { z } from 'zod';
|
|
49
|
+
|
|
50
|
+
export const schema = z.object({
|
|
51
|
+
projectName: z.string().min(1, 'Project name is required.'),
|
|
52
|
+
framework: z.string().min(1, 'Please select a framework.'),
|
|
53
|
+
rootDir: z.string().min(1, 'Root directory is required.'),
|
|
54
|
+
environments: z.array(z.string()).min(1, 'Select at least one environment.')
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export type FormValues = z.infer<typeof schema>;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Connect to React Hook Form
|
|
61
|
+
|
|
62
|
+
Pass the schema to `useForm` through `zodResolver`, and provide `defaultValues` for every field so the inputs are controlled from the first render:
|
|
63
|
+
|
|
64
|
+
```tsx title="deploy-form.tsx" showLineNumbers {6}
|
|
65
|
+
import { useForm } from 'react-hook-form';
|
|
66
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
67
|
+
import { schema, type FormValues } from './schema';
|
|
68
|
+
|
|
69
|
+
const { control, handleSubmit } = useForm<FormValues>({
|
|
70
|
+
resolver: zodResolver(schema),
|
|
71
|
+
defaultValues: { projectName: '', framework: '', rootDir: './', environments: [] }
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
<DocMessage>
|
|
76
|
+
`zodResolver` is used here, but `@hookform/resolvers` also ships resolvers for Yup, Valibot, Joi and more. Swap the resolver and keep everything else the same.
|
|
77
|
+
</DocMessage>
|
|
78
|
+
|
|
79
|
+
### Submit
|
|
80
|
+
|
|
81
|
+
Wrap your handler with `handleSubmit`. It validates first and only calls your callback when every field passes; the `values` argument is fully typed from the schema:
|
|
82
|
+
|
|
83
|
+
```tsx title="Submit" showLineNumbers
|
|
84
|
+
const onSubmit = handleSubmit((values) => {
|
|
85
|
+
console.log(values);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<form onSubmit={onSubmit} className="flex flex-col gap-6">
|
|
90
|
+
{/* fields go here */}
|
|
91
|
+
<Button type="submit">Deploy</Button>
|
|
92
|
+
</form>
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Reset
|
|
97
|
+
|
|
98
|
+
Pull `reset` from `useForm`. With no argument it clears every field back to `defaultValues`; pass an object to seed the form with new values instead, for example after a successful submit:
|
|
99
|
+
|
|
100
|
+
```tsx title="Reset"
|
|
101
|
+
const { control, handleSubmit, reset } = useForm({/* ... */});
|
|
102
|
+
|
|
103
|
+
// restore the defaults
|
|
104
|
+
<Button type="button" severity="secondary" variant="outlined" onClick={() => reset()}>
|
|
105
|
+
Reset
|
|
106
|
+
</Button>;
|
|
107
|
+
|
|
108
|
+
// or set new values
|
|
109
|
+
reset({ projectName: 'my-app', framework: 'nextjs', environments: ['production'] });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Result
|
|
113
|
+
|
|
114
|
+
Everything above comes together as the complete form. Each field is broken down in [Fields](#fields):
|
|
115
|
+
|
|
116
|
+
<div className="[&_pre]:max-h-[32rem] [&_pre]:overflow-y-auto">
|
|
117
|
+
|
|
118
|
+
```tsx title="deploy-form.tsx" showLineNumbers
|
|
119
|
+
'use client';
|
|
120
|
+
import { useForm, Controller } from 'react-hook-form';
|
|
121
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
122
|
+
import { z } from 'zod';
|
|
123
|
+
import { ChevronDown, Check } from '@primeicons/react';
|
|
124
|
+
import { InputText } from '@primereact/ui/inputtext';
|
|
125
|
+
import { Select } from '@primereact/ui/select';
|
|
126
|
+
import { Checkbox } from '@primereact/ui/checkbox';
|
|
127
|
+
import { CheckboxGroup } from '@primereact/ui/checkboxgroup';
|
|
128
|
+
import { Label } from '@primereact/ui/label';
|
|
129
|
+
import { Button } from '@primereact/ui/button';
|
|
130
|
+
import { Message } from '@primereact/ui/message';
|
|
131
|
+
|
|
132
|
+
const FRAMEWORKS = [
|
|
133
|
+
{ label: 'Next.js', value: 'nextjs' },
|
|
134
|
+
{ label: 'Vite', value: 'vite' },
|
|
135
|
+
{ label: 'Astro', value: 'astro' }
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const ENVIRONMENTS = [
|
|
139
|
+
{ label: 'Production', value: 'production' },
|
|
140
|
+
{ label: 'Preview', value: 'preview' },
|
|
141
|
+
{ label: 'Development', value: 'development' }
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
const schema = z.object({
|
|
145
|
+
projectName: z.string().min(1, 'Project name is required.'),
|
|
146
|
+
framework: z.string().min(1, 'Please select a framework.'),
|
|
147
|
+
rootDir: z.string().min(1, 'Root directory is required.'),
|
|
148
|
+
environments: z.array(z.string()).min(1, 'Select at least one environment.')
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
type FormValues = z.infer<typeof schema>;
|
|
152
|
+
|
|
153
|
+
export default function DeployForm() {
|
|
154
|
+
const { control, handleSubmit, reset } = useForm<FormValues>({
|
|
155
|
+
resolver: zodResolver(schema),
|
|
156
|
+
defaultValues: { projectName: '', framework: '', rootDir: './', environments: [] }
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const onSubmit = handleSubmit((values) => {
|
|
160
|
+
console.log(values);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<form onSubmit={onSubmit} className="flex flex-col gap-6">
|
|
165
|
+
<Controller
|
|
166
|
+
name="projectName"
|
|
167
|
+
control={control}
|
|
168
|
+
render={({ field, fieldState }) => (
|
|
169
|
+
<div className="flex flex-col gap-2">
|
|
170
|
+
<Label htmlFor="projectName">Project name</Label>
|
|
171
|
+
<InputText id="projectName" {...field} invalid={fieldState.invalid} fluid />
|
|
172
|
+
{fieldState.error && (
|
|
173
|
+
<Message.Root severity="error" variant="simple" size="small">
|
|
174
|
+
<Message.Content>
|
|
175
|
+
<Message.Text>{fieldState.error.message}</Message.Text>
|
|
176
|
+
</Message.Content>
|
|
177
|
+
</Message.Root>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
/>
|
|
182
|
+
|
|
183
|
+
<Controller
|
|
184
|
+
name="framework"
|
|
185
|
+
control={control}
|
|
186
|
+
render={({ field, fieldState }) => (
|
|
187
|
+
<div className="flex flex-col gap-2">
|
|
188
|
+
<Label htmlFor="framework">Framework preset</Label>
|
|
189
|
+
<Select.Root value={field.value} onValueChange={(e) => field.onChange(e.value)} options={FRAMEWORKS} optionLabel="label" optionValue="value" invalid={fieldState.invalid}>
|
|
190
|
+
<Select.Trigger type="button">
|
|
191
|
+
<Select.Value placeholder="Select a framework" />
|
|
192
|
+
<Select.Indicator>
|
|
193
|
+
<ChevronDown />
|
|
194
|
+
</Select.Indicator>
|
|
195
|
+
</Select.Trigger>
|
|
196
|
+
<Select.Portal>
|
|
197
|
+
<Select.Positioner>
|
|
198
|
+
<Select.Popup>
|
|
199
|
+
<Select.List />
|
|
200
|
+
</Select.Popup>
|
|
201
|
+
</Select.Positioner>
|
|
202
|
+
</Select.Portal>
|
|
203
|
+
</Select.Root>
|
|
204
|
+
{fieldState.error && (
|
|
205
|
+
<Message.Root severity="error" variant="simple" size="small">
|
|
206
|
+
<Message.Content>
|
|
207
|
+
<Message.Text>{fieldState.error.message}</Message.Text>
|
|
208
|
+
</Message.Content>
|
|
209
|
+
</Message.Root>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
/>
|
|
214
|
+
|
|
215
|
+
<Controller
|
|
216
|
+
name="rootDir"
|
|
217
|
+
control={control}
|
|
218
|
+
render={({ field, fieldState }) => (
|
|
219
|
+
<div className="flex flex-col gap-2">
|
|
220
|
+
<Label htmlFor="rootDir">Root directory</Label>
|
|
221
|
+
<InputText id="rootDir" {...field} invalid={fieldState.invalid} fluid />
|
|
222
|
+
{fieldState.error && (
|
|
223
|
+
<Message.Root severity="error" variant="simple" size="small">
|
|
224
|
+
<Message.Content>
|
|
225
|
+
<Message.Text>{fieldState.error.message}</Message.Text>
|
|
226
|
+
</Message.Content>
|
|
227
|
+
</Message.Root>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
/>
|
|
232
|
+
|
|
233
|
+
<Controller
|
|
234
|
+
name="environments"
|
|
235
|
+
control={control}
|
|
236
|
+
render={({ field, fieldState }) => (
|
|
237
|
+
<div className="flex flex-col gap-2">
|
|
238
|
+
<Label>Environments</Label>
|
|
239
|
+
<CheckboxGroup value={field.value} onValueChange={(e) => field.onChange(e.value)}>
|
|
240
|
+
{ENVIRONMENTS.map((env) => (
|
|
241
|
+
<div key={env.value} className="flex items-center gap-2">
|
|
242
|
+
<Checkbox.Root inputId={env.value} value={env.value}>
|
|
243
|
+
<Checkbox.Box>
|
|
244
|
+
<Checkbox.Indicator match="checked">
|
|
245
|
+
<Check />
|
|
246
|
+
</Checkbox.Indicator>
|
|
247
|
+
</Checkbox.Box>
|
|
248
|
+
</Checkbox.Root>
|
|
249
|
+
<Label htmlFor={env.value}>{env.label}</Label>
|
|
250
|
+
</div>
|
|
251
|
+
))}
|
|
252
|
+
</CheckboxGroup>
|
|
253
|
+
{fieldState.error && (
|
|
254
|
+
<Message.Root severity="error" variant="simple" size="small">
|
|
255
|
+
<Message.Content>
|
|
256
|
+
<Message.Text>{fieldState.error.message}</Message.Text>
|
|
257
|
+
</Message.Content>
|
|
258
|
+
</Message.Root>
|
|
259
|
+
)}
|
|
260
|
+
</div>
|
|
261
|
+
)}
|
|
262
|
+
/>
|
|
263
|
+
|
|
264
|
+
<div className="flex justify-end gap-3">
|
|
265
|
+
<Button type="button" severity="secondary" variant="outlined" onClick={() => reset()}>
|
|
266
|
+
Reset
|
|
267
|
+
</Button>
|
|
268
|
+
<Button type="submit">Deploy</Button>
|
|
269
|
+
</div>
|
|
270
|
+
</form>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
## Fields
|
|
278
|
+
|
|
279
|
+
Every input lives inside a `Controller`. How you forward the change depends on the component:
|
|
280
|
+
|
|
281
|
+
- **Native inputs**: `InputText` and `Textarea` render a real `<input>` / `<textarea>`, so spread `{...field}` (or pass `field.onChange` directly).
|
|
282
|
+
- **Composite inputs**: `Select`, `Checkbox`, `RadioButtonGroup` and `CheckboxGroup` report changes through `onValueChange` / `onCheckedChange` with an event object, so forward `event.value` / `event.checked`.
|
|
283
|
+
|
|
284
|
+
Bind `invalid={fieldState.invalid}` on any component to get error styling.
|
|
285
|
+
|
|
286
|
+
### InputText
|
|
287
|
+
|
|
288
|
+
```tsx title="InputText"
|
|
289
|
+
<Controller
|
|
290
|
+
name="projectName"
|
|
291
|
+
control={control}
|
|
292
|
+
render={({ field, fieldState }) => (
|
|
293
|
+
<div className="flex flex-col gap-2">
|
|
294
|
+
<Label htmlFor="projectName">Project name</Label>
|
|
295
|
+
<InputText id="projectName" {...field} invalid={fieldState.invalid} fluid />
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
/>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Select
|
|
302
|
+
|
|
303
|
+
```tsx title="Select"
|
|
304
|
+
<Controller
|
|
305
|
+
name="framework"
|
|
306
|
+
control={control}
|
|
307
|
+
render={({ field, fieldState }) => (
|
|
308
|
+
<Select.Root value={field.value} onValueChange={(e) => field.onChange(e.value)} options={FRAMEWORKS} optionLabel="label" optionValue="value" invalid={fieldState.invalid}>
|
|
309
|
+
<Select.Trigger type="button">
|
|
310
|
+
<Select.Value placeholder="Select a framework" />
|
|
311
|
+
<Select.Indicator>
|
|
312
|
+
<ChevronDown />
|
|
313
|
+
</Select.Indicator>
|
|
314
|
+
</Select.Trigger>
|
|
315
|
+
<Select.Portal>
|
|
316
|
+
<Select.Positioner>
|
|
317
|
+
<Select.Popup>
|
|
318
|
+
<Select.List />
|
|
319
|
+
</Select.Popup>
|
|
320
|
+
</Select.Positioner>
|
|
321
|
+
</Select.Portal>
|
|
322
|
+
</Select.Root>
|
|
323
|
+
)}
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Checkbox
|
|
328
|
+
|
|
329
|
+
A single checkbox is a boolean, so bind `checked` and forward `event.checked`:
|
|
330
|
+
|
|
331
|
+
```tsx title="Checkbox"
|
|
332
|
+
<Controller
|
|
333
|
+
name="agree"
|
|
334
|
+
control={control}
|
|
335
|
+
render={({ field }) => (
|
|
336
|
+
<Checkbox.Root checked={field.value} onCheckedChange={(e) => field.onChange(e.checked)}>
|
|
337
|
+
<Checkbox.Box>
|
|
338
|
+
<Checkbox.Indicator match="checked">
|
|
339
|
+
<Check />
|
|
340
|
+
</Checkbox.Indicator>
|
|
341
|
+
</Checkbox.Box>
|
|
342
|
+
</Checkbox.Root>
|
|
343
|
+
)}
|
|
344
|
+
/>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Groups
|
|
348
|
+
|
|
349
|
+
`RadioButtonGroup`, `CheckboxGroup` and `ToggleButtonGroup` own a single value for the whole group, so wrap the group (not each item) in one `Controller`:
|
|
350
|
+
|
|
351
|
+
```tsx title="CheckboxGroup"
|
|
352
|
+
<Controller
|
|
353
|
+
name="environments"
|
|
354
|
+
control={control}
|
|
355
|
+
render={({ field }) => (
|
|
356
|
+
<CheckboxGroup value={field.value} onValueChange={(e) => field.onChange(e.value)}>
|
|
357
|
+
{ENVIRONMENTS.map((env) => (
|
|
358
|
+
<div key={env.value} className="flex items-center gap-2">
|
|
359
|
+
<Checkbox.Root inputId={env.value} value={env.value}>
|
|
360
|
+
<Checkbox.Box>
|
|
361
|
+
<Checkbox.Indicator match="checked">
|
|
362
|
+
<Check />
|
|
363
|
+
</Checkbox.Indicator>
|
|
364
|
+
</Checkbox.Box>
|
|
365
|
+
</Checkbox.Root>
|
|
366
|
+
<Label htmlFor={env.value}>{env.label}</Label>
|
|
367
|
+
</div>
|
|
368
|
+
))}
|
|
369
|
+
</CheckboxGroup>
|
|
370
|
+
)}
|
|
371
|
+
/>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Value types
|
|
375
|
+
|
|
376
|
+
Most inputs hold a string, but some do not. Match the schema and the empty `defaultValue` to the value type:
|
|
377
|
+
|
|
378
|
+
- `InputNumber`, `Slider`, `Rating` → `number` (`z.number()`)
|
|
379
|
+
- `DatePicker` → `Date` (`z.date()`)
|
|
380
|
+
- `Checkbox`, `ToggleSwitch`, `ToggleButton` → `boolean` (`z.boolean()`)
|
|
381
|
+
- `CheckboxGroup`, multiple `Select`, `InputTags` → `array` (`z.array(...)`)
|
|
382
|
+
|
|
383
|
+
<DocMessage>
|
|
384
|
+
Number and date fields start as `null`, not `''`. An empty string breaks their value type.
|
|
385
|
+
</DocMessage>
|
|
386
|
+
|
|
387
|
+
## Validation
|
|
388
|
+
|
|
389
|
+
### When it runs
|
|
390
|
+
|
|
391
|
+
By default the form validates on submit and then re-validates a field on every change. Change this with `mode`:
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
useForm({ mode: 'onBlur', resolver: zodResolver(schema) });
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
| Mode | Validates |
|
|
398
|
+
| ---------------------- | -------------------------------------- |
|
|
399
|
+
| `onSubmit` _(default)_ | on submit, then re-validates on change |
|
|
400
|
+
| `onBlur` | when a field loses focus |
|
|
401
|
+
| `onChange` | on every keystroke / change |
|
|
402
|
+
| `onTouched` | on first blur, then on every change |
|
|
403
|
+
| `all` | on both blur and change |
|
|
404
|
+
|
|
405
|
+
`reValidateMode` (default `onChange`) controls how fields re-validate _after_ the first submit.
|
|
406
|
+
|
|
407
|
+
### Showing errors
|
|
408
|
+
|
|
409
|
+
`fieldState.error.message` holds the message resolved from the schema. Render it with the `Message` component in the `simple` variant so it sits inline under the field:
|
|
410
|
+
|
|
411
|
+
```tsx title="Error message"
|
|
412
|
+
<Controller
|
|
413
|
+
name="projectName"
|
|
414
|
+
control={control}
|
|
415
|
+
render={({ field, fieldState }) => (
|
|
416
|
+
<div className="flex flex-col gap-2">
|
|
417
|
+
<Label htmlFor="projectName">Project name</Label>
|
|
418
|
+
<InputText id="projectName" {...field} invalid={fieldState.invalid} fluid />
|
|
419
|
+
{fieldState.error && (
|
|
420
|
+
<Message.Root severity="error" variant="simple" size="small">
|
|
421
|
+
<Message.Content>
|
|
422
|
+
<Message.Text>{fieldState.error.message}</Message.Text>
|
|
423
|
+
</Message.Content>
|
|
424
|
+
</Message.Root>
|
|
425
|
+
)}
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
/>
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Array fields
|
|
432
|
+
|
|
433
|
+
For a repeatable set of fields, such as environment variables, describe the item shape as an array in the schema and drive the rows with `useFieldArray`:
|
|
434
|
+
|
|
435
|
+
```tsx title="schema" showLineNumbers
|
|
436
|
+
const schema = z.object({
|
|
437
|
+
envVars: z.array(
|
|
438
|
+
z.object({
|
|
439
|
+
key: z.string().min(1, 'Key is required.'),
|
|
440
|
+
value: z.string().min(1, 'Value is required.')
|
|
441
|
+
})
|
|
442
|
+
)
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
`useFieldArray` returns the current rows plus helpers to add and remove them. Each input targets its row with a dotted `name` (`envVars.0.key`):
|
|
447
|
+
|
|
448
|
+
```tsx title="Array fields" showLineNumbers {1,7,8}
|
|
449
|
+
const { fields, append, remove } = useFieldArray({ control, name: 'envVars' });
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
<div className="flex flex-col gap-3">
|
|
453
|
+
{fields.map((row, i) => (
|
|
454
|
+
<div key={row.id} className="flex items-start gap-2">
|
|
455
|
+
<Controller name={`envVars.${i}.key`} control={control} render={({ field, fieldState }) => <InputText {...field} invalid={fieldState.invalid} placeholder="KEY" />} />
|
|
456
|
+
<Controller name={`envVars.${i}.value`} control={control} render={({ field, fieldState }) => <InputText {...field} invalid={fieldState.invalid} placeholder="value" />} />
|
|
457
|
+
<Button type="button" severity="secondary" variant="text" onClick={() => remove(i)}>
|
|
458
|
+
<Times />
|
|
459
|
+
</Button>
|
|
460
|
+
</div>
|
|
461
|
+
))}
|
|
462
|
+
<Button type="button" variant="outlined" onClick={() => append({ key: '', value: '' })}>
|
|
463
|
+
Add variable
|
|
464
|
+
</Button>
|
|
465
|
+
</div>
|
|
466
|
+
);
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
<DocMessage>
|
|
470
|
+
Use `row.id` from `fields` as the React `key`, not the array index. `useFieldArray` keeps these ids stable across add and remove.
|
|
471
|
+
</DocMessage>
|
|
472
|
+
|
|
473
|
+
## Nested objects
|
|
474
|
+
|
|
475
|
+
Group related fields under an object in the schema and target them with a dotted `name`:
|
|
476
|
+
|
|
477
|
+
```tsx title="schema"
|
|
478
|
+
const schema = z.object({
|
|
479
|
+
build: z.object({
|
|
480
|
+
command: z.string().min(1, 'Build command is required.'),
|
|
481
|
+
install: z.string().min(1, 'Install command is required.')
|
|
482
|
+
})
|
|
483
|
+
});
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
```tsx title="Nested fields"
|
|
487
|
+
<Controller
|
|
488
|
+
name="build.command"
|
|
489
|
+
control={control}
|
|
490
|
+
render={({ field, fieldState }) => (
|
|
491
|
+
<InputText {...field} invalid={fieldState.invalid} placeholder="npm run build" />
|
|
492
|
+
)}
|
|
493
|
+
/>
|
|
494
|
+
<Controller
|
|
495
|
+
name="build.install"
|
|
496
|
+
control={control}
|
|
497
|
+
render={({ field, fieldState }) => (
|
|
498
|
+
<InputText {...field} invalid={fieldState.invalid} placeholder="npm install" />
|
|
499
|
+
)}
|
|
500
|
+
/>
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Defaults follow the same shape: `defaultValues: { build: { command: '', install: '' } }`.
|