0
# Forms & Data Input
1
2
Advanced form management with auto-save, validation, mutation handling, and integration with popular form libraries.
3
4
## Capabilities
5
6
### Core Form Management
7
8
#### useForm Hook
9
10
Orchestrates data hooks for create, edit, and clone operations with advanced features like auto-save, validation, and automatic redirects.
11
12
```typescript { .api }
13
/**
14
* Orchestrates form operations with data management, validation, and auto-save
15
* @param params - Form configuration options
16
* @returns Form state, handlers, and mutation controls
17
*/
18
function useForm<TQueryFnData = BaseRecord, TError = HttpError, TVariables = {}, TData = TQueryFnData, TResponse = BaseRecord, TResponseError = HttpError>(
19
params?: UseFormConfig<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError>
20
): UseFormReturnType<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError>;
21
22
interface UseFormConfig<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError> {
23
/** Form action type - determines behavior */
24
action?: "create" | "edit" | "clone";
25
/** Resource name - inferred from route if not provided */
26
resource?: string;
27
/** Record ID for edit/clone operations */
28
id?: BaseKey;
29
/** Where to redirect after successful submission */
30
redirect?: RedirectAction;
31
/** Additional metadata for operations */
32
meta?: MetaQuery;
33
/** Mutation mode for optimistic updates */
34
mutationMode?: MutationMode;
35
/** Callback on successful mutation */
36
onMutationSuccess?: (data: TResponse, variables: TVariables, context: any, isAutoSave: boolean) => void;
37
/** Callback on mutation error */
38
onMutationError?: (error: TResponseError, variables: TVariables, context: any, isAutoSave: boolean) => void;
39
/** Auto-save configuration */
40
autoSave?: {
41
/** Enable auto-save functionality */
42
enabled?: boolean;
43
/** Debounce delay in milliseconds */
44
debounce?: number;
45
/** Invalidate queries when component unmounts */
46
invalidateOnUnmount?: boolean;
47
};
48
/** Success notification configuration */
49
successNotification?: SuccessErrorNotification | false;
50
/** Error notification configuration */
51
errorNotification?: SuccessErrorNotification | false;
52
/** Data provider name to use */
53
dataProviderName?: string;
54
/** Query options for data fetching */
55
queryOptions?: UseQueryOptions<TQueryFnData, TError>;
56
/** Mutation options for create/update */
57
createMutationOptions?: UseMutationOptions<TResponse, TResponseError, UseCreateParams<TVariables>>;
58
/** Mutation options for update operations */
59
updateMutationOptions?: UseMutationOptions<TResponse, TResponseError, UseUpdateParams<TVariables>>;
60
/** Timeout for undoable mutations */
61
undoableTimeout?: number;
62
/** Cache invalidation configuration */
63
invalidates?: Array<string>;
64
}
65
66
interface UseFormReturnType<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError> {
67
/** Form submission handler */
68
onFinish: (values: TVariables) => Promise<void>;
69
/** Auto-save handler for form changes */
70
onFinishAutoSave: (values: TVariables) => Promise<void>;
71
/** Whether form operations are loading */
72
formLoading: boolean;
73
/** Mutation result for create/update operations */
74
mutation: UseMutationResult<TResponse, TResponseError, UseCreateParams<TVariables> | UseUpdateParams<TVariables>>;
75
/** Query result for fetching existing data (edit/clone) */
76
query: UseQueryResult<TQueryFnData, TError>;
77
/** Auto-save props for form libraries */
78
autoSaveProps: AutoSaveProps;
79
/** Current record ID */
80
id?: BaseKey;
81
/** Function to set record ID */
82
setId: React.Dispatch<React.SetStateAction<BaseKey | undefined>>;
83
/** Redirect configuration */
84
redirect: RedirectAction;
85
/** Loading overtime information */
86
overtime: UseLoadingOvertimeReturnType;
87
}
88
89
interface AutoSaveProps {
90
/** Current auto-save status */
91
status: "loading" | "success" | "error" | "idle";
92
/** Error from auto-save operation */
93
error?: TResponseError;
94
/** Data from successful auto-save */
95
data?: TResponse;
96
}
97
```
98
99
**Usage Example:**
100
101
```typescript
102
import { useForm } from "@refinedev/core";
103
import { useEffect } from "react";
104
105
interface PostFormData {
106
title: string;
107
content: string;
108
status: "draft" | "published";
109
categoryId: number;
110
}
111
112
function PostForm() {
113
const {
114
onFinish,
115
onFinishAutoSave,
116
formLoading,
117
query,
118
autoSaveProps,
119
id,
120
setId
121
} = useForm<PostFormData>({
122
action: "edit", // or "create", "clone"
123
resource: "posts",
124
redirect: "list",
125
autoSave: {
126
enabled: true,
127
debounce: 2000 // Auto-save after 2 seconds of inactivity
128
},
129
onMutationSuccess: (data, variables, context, isAutoSave) => {
130
if (isAutoSave) {
131
console.log("Auto-saved successfully");
132
} else {
133
console.log("Form submitted successfully");
134
}
135
}
136
});
137
138
const [formData, setFormData] = useState<PostFormData>({
139
title: "",
140
content: "",
141
status: "draft",
142
categoryId: 1
143
});
144
145
// Load existing data for edit mode
146
useEffect(() => {
147
if (query.data) {
148
setFormData(query.data.data);
149
}
150
}, [query.data]);
151
152
// Auto-save on form changes
153
useEffect(() => {
154
const timeoutId = setTimeout(() => {
155
onFinishAutoSave(formData);
156
}, 2000);
157
158
return () => clearTimeout(timeoutId);
159
}, [formData, onFinishAutoSave]);
160
161
const handleSubmit = async (e: React.FormEvent) => {
162
e.preventDefault();
163
await onFinish(formData);
164
};
165
166
return (
167
<form onSubmit={handleSubmit}>
168
<div>
169
<label>Title:</label>
170
<input
171
value={formData.title}
172
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
173
disabled={formLoading}
174
/>
175
</div>
176
177
<div>
178
<label>Content:</label>
179
<textarea
180
value={formData.content}
181
onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
182
disabled={formLoading}
183
/>
184
</div>
185
186
<div>
187
<label>Status:</label>
188
<select
189
value={formData.status}
190
onChange={(e) => setFormData(prev => ({ ...prev, status: e.target.value as "draft" | "published" }))}
191
disabled={formLoading}
192
>
193
<option value="draft">Draft</option>
194
<option value="published">Published</option>
195
</select>
196
</div>
197
198
{/* Auto-save indicator */}
199
<div className="auto-save-status">
200
{autoSaveProps.status === "loading" && <span>Saving...</span>}
201
{autoSaveProps.status === "success" && <span>Saved ✓</span>}
202
{autoSaveProps.status === "error" && <span>Save failed ✗</span>}
203
</div>
204
205
<button type="submit" disabled={formLoading}>
206
{formLoading ? "Submitting..." : "Submit"}
207
</button>
208
</form>
209
);
210
}
211
```
212
213
### Form Library Integrations
214
215
#### React Hook Form Integration
216
217
Integration patterns with React Hook Form for advanced form validation and state management.
218
219
```typescript { .api }
220
/**
221
* Integration with React Hook Form
222
* @param params - Configuration for React Hook Form integration
223
* @returns Combined form state and Refine form handlers
224
*/
225
function useFormWithReactHookForm<TFormData, TQueryFnData = BaseRecord, TError = HttpError, TResponse = BaseRecord>(
226
params?: UseFormConfig<TQueryFnData, TError, TFormData, TQueryFnData, TResponse, TError>
227
): UseFormWithReactHookFormReturnType<TFormData, TQueryFnData, TError, TResponse>;
228
229
interface UseFormWithReactHookFormReturnType<TFormData, TQueryFnData, TError, TResponse> extends UseFormReturnType<TQueryFnData, TError, TFormData, TQueryFnData, TResponse, TError> {
230
/** Register form fields with React Hook Form */
231
register: UseFormRegister<TFormData>;
232
/** Handle form submission with validation */
233
handleSubmit: UseFormHandleSubmit<TFormData>;
234
/** Form state and errors */
235
formState: FormState<TFormData>;
236
/** Set form values programmatically */
237
setValue: UseFormSetValue<TFormData>;
238
/** Get form values */
239
getValues: UseFormGetValues<TFormData>;
240
/** Watch form field changes */
241
watch: UseFormWatch<TFormData>;
242
}
243
```
244
245
**React Hook Form Example:**
246
247
```typescript
248
import { useForm as useRefineForm } from "@refinedev/core";
249
import { useForm as useReactHookForm } from "react-hook-form";
250
import { yupResolver } from "@hookform/resolvers/yup";
251
import * as yup from "yup";
252
253
const schema = yup.object({
254
title: yup.string().required("Title is required"),
255
content: yup.string().min(10, "Content must be at least 10 characters"),
256
email: yup.string().email("Invalid email format")
257
});
258
259
function PostFormWithValidation() {
260
const { onFinish, formLoading, query } = useRefineForm();
261
262
const {
263
register,
264
handleSubmit,
265
formState: { errors },
266
setValue,
267
watch
268
} = useReactHookForm({
269
resolver: yupResolver(schema),
270
defaultValues: query.data?.data
271
});
272
273
const onSubmit = (data: any) => {
274
onFinish(data);
275
};
276
277
return (
278
<form onSubmit={handleSubmit(onSubmit)}>
279
<div>
280
<input {...register("title")} placeholder="Title" />
281
{errors.title && <span>{errors.title.message}</span>}
282
</div>
283
284
<div>
285
<textarea {...register("content")} placeholder="Content" />
286
{errors.content && <span>{errors.content.message}</span>}
287
</div>
288
289
<button type="submit" disabled={formLoading}>
290
Submit
291
</button>
292
</form>
293
);
294
}
295
```
296
297
#### Formik Integration
298
299
Integration patterns with Formik for form state management and validation.
300
301
```typescript { .api }
302
/**
303
* Integration with Formik
304
* @param params - Configuration for Formik integration
305
* @returns Formik props combined with Refine form handlers
306
*/
307
function useFormWithFormik<TFormData>(
308
params?: UseFormConfig & FormikConfig<TFormData>
309
): UseFormWithFormikReturnType<TFormData>;
310
311
interface UseFormWithFormikReturnType<TFormData> {
312
/** Formik form props */
313
formikProps: FormikProps<TFormData>;
314
/** Refine form handlers */
315
refineProps: UseFormReturnType;
316
}
317
```
318
319
### Auto-Save & Draft Management
320
321
#### AutoSaveIndicator Component
322
323
Visual indicator for auto-save status with customizable appearance.
324
325
```typescript { .api }
326
/**
327
* Visual indicator for auto-save status
328
* @param props - Auto-save indicator configuration
329
* @returns Auto-save status indicator component
330
*/
331
function AutoSaveIndicator(props?: AutoSaveIndicatorProps): JSX.Element;
332
333
interface AutoSaveIndicatorProps {
334
/** Auto-save status */
335
status: "loading" | "success" | "error" | "idle";
336
/** Custom messages for each status */
337
messages?: {
338
loading?: string;
339
success?: string;
340
error?: string;
341
idle?: string;
342
};
343
/** Custom styling */
344
style?: React.CSSProperties;
345
/** Custom class names */
346
className?: string;
347
}
348
```
349
350
**Usage Example:**
351
352
```typescript
353
import { AutoSaveIndicator, useForm } from "@refinedev/core";
354
355
function FormWithAutoSave() {
356
const { autoSaveProps, onFinishAutoSave } = useForm({
357
autoSave: { enabled: true, debounce: 1000 }
358
});
359
360
return (
361
<div>
362
<form>
363
{/* Form fields */}
364
</form>
365
366
<AutoSaveIndicator
367
status={autoSaveProps.status}
368
messages={{
369
loading: "Saving draft...",
370
success: "Draft saved",
371
error: "Failed to save draft"
372
}}
373
/>
374
</div>
375
);
376
}
377
```
378
379
### Form Validation
380
381
#### Client-Side Validation
382
383
Integration with validation libraries for client-side form validation.
384
385
```typescript { .api }
386
/**
387
* Validation configuration for forms
388
*/
389
interface ValidationConfig<TFormData> {
390
/** Validation schema */
391
schema?: ValidationSchema<TFormData>;
392
/** Custom validation functions */
393
rules?: ValidationRules<TFormData>;
394
/** Validation mode */
395
mode?: "onChange" | "onBlur" | "onSubmit";
396
/** Re-validation mode */
397
reValidateMode?: "onChange" | "onBlur" | "onSubmit";
398
}
399
400
interface ValidationSchema<TFormData> {
401
[K in keyof TFormData]?: ValidationRule[];
402
}
403
404
interface ValidationRule {
405
/** Validation type */
406
type: "required" | "email" | "min" | "max" | "pattern" | "custom";
407
/** Validation value */
408
value?: any;
409
/** Error message */
410
message: string;
411
}
412
413
interface ValidationRules<TFormData> {
414
[K in keyof TFormData]?: (value: TFormData[K], formData: TFormData) => string | undefined;
415
}
416
```
417
418
### Form State Management
419
420
#### Form Loading States
421
422
Managing different loading states during form operations.
423
424
```typescript { .api }
425
/**
426
* Form loading state management
427
*/
428
interface FormLoadingState {
429
/** Whether form is submitting */
430
submitting: boolean;
431
/** Whether data is being fetched */
432
fetching: boolean;
433
/** Whether auto-save is in progress */
434
autoSaving: boolean;
435
/** Whether form is validating */
436
validating: boolean;
437
}
438
```
439
440
#### Form Error Handling
441
442
Comprehensive error handling for form operations.
443
444
```typescript { .api }
445
/**
446
* Form error handling configuration
447
*/
448
interface FormErrorHandling<TError> {
449
/** Field-specific errors */
450
fieldErrors?: Record<string, string[]>;
451
/** General form errors */
452
formErrors?: string[];
453
/** Server validation errors */
454
serverErrors?: TError;
455
/** Error display mode */
456
errorMode?: "field" | "summary" | "both";
457
}
458
```
459
460
### Advanced Form Features
461
462
#### Multi-Step Forms
463
464
Support for multi-step form workflows with data persistence.
465
466
```typescript { .api }
467
/**
468
* Multi-step form management
469
*/
470
interface MultiStepFormConfig {
471
/** Current step index */
472
currentStep: number;
473
/** Total number of steps */
474
totalSteps: number;
475
/** Step validation configuration */
476
stepValidation?: Record<number, ValidationConfig>;
477
/** Data persistence between steps */
478
persistData?: boolean;
479
}
480
481
interface MultiStepFormActions {
482
/** Go to next step */
483
nextStep: () => void;
484
/** Go to previous step */
485
previousStep: () => void;
486
/** Go to specific step */
487
goToStep: (step: number) => void;
488
/** Complete the form */
489
complete: () => void;
490
}
491
```
492
493
#### Dynamic Forms
494
495
Support for forms with dynamic field generation and conditional logic.
496
497
```typescript { .api }
498
/**
499
* Dynamic form field configuration
500
*/
501
interface DynamicFieldConfig {
502
/** Field type */
503
type: "text" | "number" | "select" | "checkbox" | "radio" | "textarea" | "date";
504
/** Field name/key */
505
name: string;
506
/** Display label */
507
label: string;
508
/** Default value */
509
defaultValue?: any;
510
/** Field options for select/radio */
511
options?: Array<{ label: string; value: any }>;
512
/** Validation rules */
513
validation?: ValidationRule[];
514
/** Conditional display logic */
515
condition?: (formData: any) => boolean;
516
/** Field-specific props */
517
props?: Record<string, any>;
518
}
519
```
520
521
## Types
522
523
```typescript { .api }
524
type RedirectAction = "list" | "edit" | "show" | "create" | "clone" | false;
525
526
interface UseLoadingOvertimeReturnType {
527
/** Elapsed time since loading started */
528
elapsedTime?: number;
529
}
530
531
interface FormSubmissionResult<TData> {
532
/** Whether submission was successful */
533
success: boolean;
534
/** Response data */
535
data?: TData;
536
/** Error information */
537
error?: any;
538
/** Redirect configuration */
539
redirect?: {
540
to: string;
541
type?: "push" | "replace";
542
};
543
}
544
545
interface FormFieldProps {
546
/** Field name */
547
name: string;
548
/** Field value */
549
value: any;
550
/** Change handler */
551
onChange: (value: any) => void;
552
/** Blur handler */
553
onBlur?: () => void;
554
/** Field error */
555
error?: string;
556
/** Whether field is disabled */
557
disabled?: boolean;
558
/** Whether field is required */
559
required?: boolean;
560
}
561
```