0
# Form System and Utilities
1
2
Complete form management system with validation, state handling, submission, and field rendering capabilities for building dynamic forms in Payload CMS admin interfaces.
3
4
## Form Components
5
6
### Form
7
8
Main form wrapper component that provides form context and handles state management.
9
10
```typescript { .api }
11
interface FormProps {
12
onSubmit?: (data: Record<string, unknown>, actions: FormActions) => void | Promise<void>;
13
onChange?: (data: Record<string, unknown>) => void;
14
initialState?: Record<string, unknown>;
15
disabled?: boolean;
16
readOnly?: boolean;
17
validationOperation?: 'create' | 'update';
18
className?: string;
19
children?: React.ReactNode;
20
method?: 'POST' | 'PATCH';
21
action?: string;
22
encType?: string;
23
}
24
25
interface FormActions {
26
reset: () => void;
27
submit: () => Promise<void>;
28
validate: () => boolean;
29
}
30
31
function Form(props: FormProps): JSX.Element;
32
```
33
34
Usage example:
35
```typescript
36
import { Form, TextField, FormSubmit } from '@payloadcms/ui';
37
38
function DocumentForm() {
39
const handleSubmit = async (data, { reset }) => {
40
try {
41
await saveDocument(data);
42
reset();
43
} catch (error) {
44
console.error('Save failed:', error);
45
}
46
};
47
48
return (
49
<Form onSubmit={handleSubmit}>
50
<TextField path="title" label="Title" required />
51
<TextField path="content" label="Content" />
52
<FormSubmit>Save Document</FormSubmit>
53
</Form>
54
);
55
}
56
```
57
58
### FormSubmit
59
60
Form submission button component.
61
62
```typescript { .api }
63
interface FormSubmitProps {
64
children?: React.ReactNode;
65
disabled?: boolean;
66
processing?: boolean;
67
type?: 'submit' | 'button';
68
className?: string;
69
}
70
71
function FormSubmit(props: FormSubmitProps): JSX.Element;
72
```
73
74
### RenderFields
75
76
Dynamically render form fields from configuration.
77
78
```typescript { .api }
79
interface RenderFieldsProps {
80
fields: FieldConfig[];
81
path?: string;
82
margins?: boolean;
83
className?: string;
84
readOnly?: boolean;
85
permissions?: Record<string, unknown>;
86
}
87
88
function RenderFields(props: RenderFieldsProps): JSX.Element;
89
```
90
91
Usage example:
92
```typescript
93
import { RenderFields } from '@payloadcms/ui';
94
95
function DynamicForm({ fieldConfig }) {
96
return (
97
<Form>
98
<RenderFields
99
fields={fieldConfig}
100
margins={true}
101
/>
102
</Form>
103
);
104
}
105
```
106
107
### RowLabel
108
109
Label component for repeatable row fields.
110
111
```typescript { .api }
112
interface RowLabelProps {
113
data: Record<string, unknown>;
114
index: number;
115
path: string;
116
label?: string;
117
fallback?: string;
118
}
119
120
function RowLabel(props: RowLabelProps): JSX.Element;
121
```
122
123
## Form State Management
124
125
### Field Reducer
126
127
Core field state reducer for managing field values and validation.
128
129
```typescript { .api }
130
interface FieldAction {
131
type: 'UPDATE' | 'VALIDATE' | 'RESET' | 'REMOVE' | 'REPLACE_STATE';
132
path: string;
133
value?: unknown;
134
validate?: boolean;
135
errorMessage?: string;
136
disableFormData?: boolean;
137
}
138
139
function fieldReducer(
140
state: FormState,
141
action: FieldAction
142
): FormState;
143
144
interface FormState {
145
[path: string]: FieldState;
146
}
147
148
interface FieldState {
149
value: unknown;
150
valid: boolean;
151
errorMessage?: string;
152
initialValue?: unknown;
153
disableFormData?: boolean;
154
}
155
```
156
157
### Form Context Hooks
158
159
Access form state and actions through context hooks.
160
161
```typescript { .api }
162
function useFormFields<T>(
163
selector: (fields: FormFieldsContextType) => T
164
): T;
165
166
function useAllFormFields(): FormFieldsContextType;
167
168
function useFormSubmitted(): boolean;
169
function useFormProcessing(): boolean;
170
function useFormBackgroundProcessing(): boolean;
171
function useFormModified(): boolean;
172
function useFormInitializing(): boolean;
173
```
174
175
### Form Watch Hooks
176
177
Monitor form state changes.
178
179
```typescript { .api }
180
function useWatchForm<T>(): {
181
getDataByPath: (path: string) => unknown;
182
getData: () => Record<string, unknown>;
183
getSiblingData: (path: string) => Record<string, unknown>;
184
dispatchFields: (action: FieldAction) => void;
185
};
186
```
187
188
## Form Utilities
189
190
### withCondition
191
192
Higher-order component for conditional field rendering.
193
194
```typescript { .api }
195
function withCondition<T extends Record<string, unknown>>(
196
Component: React.ComponentType<T>
197
): React.ComponentType<T & ConditionalProps>;
198
199
interface ConditionalProps {
200
admin?: {
201
condition?: (data: Record<string, unknown>, siblingData?: Record<string, unknown>) => boolean;
202
};
203
}
204
```
205
206
### WatchCondition
207
208
Component for watching conditional field logic.
209
210
```typescript { .api }
211
interface WatchConditionProps {
212
path?: string;
213
condition: (data: Record<string, unknown>, siblingData?: Record<string, unknown>) => boolean;
214
children: React.ReactNode;
215
}
216
217
function WatchCondition(props: WatchConditionProps): JSX.Element | null;
218
```
219
220
### WatchChildErrors
221
222
Component for watching and handling child field errors.
223
224
```typescript { .api }
225
interface WatchChildErrorsProps {
226
path: string;
227
children?: React.ReactNode;
228
}
229
230
function WatchChildErrors(props: WatchChildErrorsProps): JSX.Element;
231
```
232
233
### NullifyLocaleField
234
235
Field component for nullifying locale-specific data.
236
237
```typescript { .api }
238
interface NullifyLocaleFieldProps {
239
path: string;
240
locale?: string;
241
}
242
243
function NullifyLocaleField(props: NullifyLocaleFieldProps): JSX.Element;
244
```
245
246
## Row Label Context
247
248
### RowLabelProvider
249
250
Context provider for row label data.
251
252
```typescript { .api }
253
interface RowLabelProviderProps {
254
children: React.ReactNode;
255
data: Record<string, unknown>;
256
index?: number;
257
path: string;
258
}
259
260
function RowLabelProvider(props: RowLabelProviderProps): JSX.Element;
261
```
262
263
### useRowLabel
264
265
Hook to access row label context data.
266
267
```typescript { .api }
268
function useRowLabel(): {
269
data: Record<string, unknown>;
270
index?: number;
271
path: string;
272
};
273
```
274
275
## Advanced Form Patterns
276
277
### Multi-Step Forms
278
279
```typescript
280
import { Form, useForm, SetStepNav } from '@payloadcms/ui';
281
282
function MultiStepForm() {
283
const { getData, validate } = useForm();
284
const [currentStep, setCurrentStep] = useState(0);
285
286
const steps = [
287
{ label: 'Basic Info', fields: basicFields },
288
{ label: 'Details', fields: detailFields },
289
{ label: 'Review', fields: [] }
290
];
291
292
const handleNext = () => {
293
if (validate()) {
294
setCurrentStep(prev => prev + 1);
295
}
296
};
297
298
return (
299
<Form>
300
<SetStepNav steps={steps} currentStep={currentStep} />
301
<RenderFields fields={steps[currentStep].fields} />
302
<button onClick={handleNext}>Next</button>
303
</Form>
304
);
305
}
306
```
307
308
### Dynamic Field Configuration
309
310
```typescript
311
import { RenderFields, useFormFields } from '@payloadcms/ui';
312
313
function ConditionalFields() {
314
const formData = useFormFields(fields => fields);
315
316
const getFieldsForType = (type: string) => {
317
switch (type) {
318
case 'article':
319
return articleFields;
320
case 'gallery':
321
return galleryFields;
322
default:
323
return baseFields;
324
}
325
};
326
327
const currentFields = getFieldsForType(formData.type?.value as string);
328
329
return <RenderFields fields={currentFields} />;
330
}
331
```
332
333
### Form Validation Patterns
334
335
```typescript
336
import { useField } from '@payloadcms/ui';
337
338
function ValidatedEmailField() {
339
const { value, setValue, showError, errorMessage } = useField<string>({
340
path: 'email',
341
validate: (val) => {
342
if (!val) return 'Email is required';
343
if (!/\S+@\S+\.\S+/.test(val)) return 'Invalid email format';
344
return true;
345
}
346
});
347
348
return (
349
<div>
350
<input
351
type="email"
352
value={value || ''}
353
onChange={(e) => setValue(e.target.value)}
354
/>
355
{showError && <span className="error">{errorMessage}</span>}
356
</div>
357
);
358
}
359
```
360
361
## Types
362
363
```typescript { .api }
364
interface FormFieldsContextType {
365
[path: string]: FieldState;
366
}
367
368
interface FieldState {
369
value: unknown;
370
valid: boolean;
371
errorMessage?: string;
372
initialValue?: unknown;
373
disableFormData?: boolean;
374
}
375
376
interface FieldConfig {
377
type: string;
378
name: string;
379
label?: string;
380
required?: boolean;
381
admin?: {
382
readOnly?: boolean;
383
disabled?: boolean;
384
hidden?: boolean;
385
condition?: (data: Record<string, unknown>) => boolean;
386
description?: string;
387
placeholder?: string;
388
};
389
validate?: (value: unknown, options: ValidateOptions) => string | true;
390
}
391
392
interface ValidateOptions {
393
data: Record<string, unknown>;
394
siblingData: Record<string, unknown>;
395
operation: 'create' | 'update';
396
id?: string | number;
397
}
398
399
interface ConditionalProps {
400
admin?: {
401
condition?: (
402
data: Record<string, unknown>,
403
siblingData?: Record<string, unknown>
404
) => boolean;
405
};
406
}
407
```