0
# Form Actions
1
2
Composables for programmatically controlling form behavior, validation, state mutations, and submission handling. These composables provide imperative APIs for complex form interactions and integrations.
3
4
## Capabilities
5
6
### Validation Actions
7
8
#### useValidateForm Composable
9
10
Validates the entire form programmatically.
11
12
```typescript { .api }
13
/**
14
* Validates entire form programmatically
15
* @returns Function to trigger form validation
16
*/
17
function useValidateForm(): (opts?: Partial<ValidationOptions>) => Promise<FormValidationResult>;
18
19
interface ValidationOptions {
20
mode: SchemaValidationMode;
21
warn: boolean;
22
}
23
24
type SchemaValidationMode = 'validated-only' | 'silent' | 'force';
25
```
26
27
#### useValidateField Composable
28
29
Validates a specific field programmatically.
30
31
```typescript { .api }
32
/**
33
* Validates specific field programmatically
34
* @returns Function to trigger field validation
35
*/
36
function useValidateField(): <TPath extends Path<any>>(
37
path: TPath,
38
opts?: Partial<ValidationOptions>
39
) => Promise<ValidationResult>;
40
```
41
42
**Validation Action Examples:**
43
44
```typescript
45
import { useValidateForm, useValidateField, useForm } from "vee-validate";
46
47
const { handleSubmit } = useForm();
48
const validateForm = useValidateForm();
49
const validateField = useValidateField();
50
51
// Manual form validation
52
const checkFormValidity = async () => {
53
const result = await validateForm();
54
55
if (!result.valid) {
56
console.log('Form validation failed:', result.errors);
57
58
// Focus first invalid field
59
const firstErrorField = Object.keys(result.errors)[0];
60
if (firstErrorField) {
61
document.querySelector(`[name="${firstErrorField}"]`)?.focus();
62
}
63
} else {
64
console.log('Form is valid:', result.values);
65
}
66
67
return result;
68
};
69
70
// Validate specific field
71
const checkEmailValidity = async () => {
72
const result = await validateField('email');
73
74
if (!result.valid) {
75
console.log('Email validation failed:', result.errors);
76
}
77
78
return result;
79
};
80
81
// Validation with different modes
82
const validateWithMode = async () => {
83
// Only validate previously validated fields
84
const validatedOnly = await validateForm({ mode: 'validated-only' });
85
86
// Validate without updating field states
87
const silent = await validateForm({ mode: 'silent' });
88
89
// Force validate all fields
90
const force = await validateForm({ mode: 'force' });
91
92
return { validatedOnly, silent, force };
93
};
94
95
// Progressive validation
96
const progressiveValidation = async () => {
97
const steps = ['email', 'password', 'confirmPassword', 'profile.name'];
98
99
for (const step of steps) {
100
const result = await validateField(step);
101
102
if (!result.valid) {
103
console.log(`Validation failed at step: ${step}`, result.errors);
104
return { success: false, failedAt: step };
105
}
106
}
107
108
return { success: true };
109
};
110
```
111
112
### Submission Actions
113
114
#### useSubmitForm Composable
115
116
Creates form submission handler with validation and error handling.
117
118
```typescript { .api }
119
/**
120
* Creates form submission handler
121
* @returns Function to create submission handler
122
*/
123
function useSubmitForm(): <TReturn = unknown>(
124
onSubmit?: SubmissionHandler<any, any, TReturn>,
125
onInvalidSubmit?: InvalidSubmissionHandler
126
) => (e?: Event) => Promise<TReturn | undefined>;
127
128
type SubmissionHandler<TInput extends GenericObject, TOutput = TInput, TReturn = unknown> = (
129
values: TOutput,
130
ctx: SubmissionContext<TInput>
131
) => TReturn;
132
133
interface SubmissionContext<TInput extends GenericObject> extends FormActions<TInput> {
134
evt?: Event;
135
controlledValues: Partial<TInput>;
136
}
137
138
type InvalidSubmissionHandler<TInput extends GenericObject = GenericObject, TOutput extends GenericObject = TInput> = (
139
ctx: InvalidSubmissionContext<TInput, TOutput>
140
) => void;
141
```
142
143
#### useResetForm Composable
144
145
Resets form to initial or specified state.
146
147
```typescript { .api }
148
/**
149
* Resets form to initial or specified state
150
* @returns Function to reset form
151
*/
152
function useResetForm(): (state?: Partial<FormState>, opts?: Partial<ResetFormOpts>) => void;
153
154
interface ResetFormOpts {
155
force: boolean;
156
}
157
```
158
159
#### useSubmitCount Composable
160
161
Gets number of form submission attempts.
162
163
```typescript { .api }
164
/**
165
* Gets number of form submission attempts
166
* @returns Computed ref to submit count
167
*/
168
function useSubmitCount(): ComputedRef<number>;
169
```
170
171
**Submission Action Examples:**
172
173
```typescript
174
import {
175
useSubmitForm,
176
useResetForm,
177
useSubmitCount,
178
useForm
179
} from "vee-validate";
180
181
const { handleSubmit } = useForm();
182
const submitForm = useSubmitForm();
183
const resetForm = useResetForm();
184
const submitCount = useSubmitCount();
185
186
// Basic form submission
187
const onSubmit = submitForm(
188
async (values) => {
189
console.log('Submitting form:', values);
190
191
const response = await fetch('/api/submit', {
192
method: 'POST',
193
headers: { 'Content-Type': 'application/json' },
194
body: JSON.stringify(values)
195
});
196
197
if (!response.ok) {
198
throw new Error('Submission failed');
199
}
200
201
return response.json();
202
},
203
({ errors, results }) => {
204
console.log('Form validation failed:', errors);
205
console.log('Validation results:', results);
206
}
207
);
208
209
// Advanced submission with error handling
210
const onAdvancedSubmit = submitForm(
211
async (values, { setFieldError, setErrors, evt }) => {
212
try {
213
// Show loading state
214
const loadingToast = showToast('Submitting...');
215
216
const response = await fetch('/api/submit', {
217
method: 'POST',
218
body: JSON.stringify(values)
219
});
220
221
const result = await response.json();
222
223
if (!response.ok) {
224
// Handle server validation errors
225
if (result.fieldErrors) {
226
Object.entries(result.fieldErrors).forEach(([field, error]) => {
227
setFieldError(field, error as string);
228
});
229
} else {
230
setFieldError('', result.message || 'Submission failed');
231
}
232
233
hideToast(loadingToast);
234
return;
235
}
236
237
// Success
238
hideToast(loadingToast);
239
showToast('Form submitted successfully!');
240
241
return result;
242
243
} catch (error) {
244
console.error('Submission error:', error);
245
setFieldError('', 'Network error. Please try again.');
246
}
247
},
248
({ errors, values, evt }) => {
249
// Handle client-side validation failures
250
const errorCount = Object.keys(errors).length;
251
showToast(`Please fix ${errorCount} validation error(s)`);
252
253
// Focus first error field
254
const firstErrorField = Object.keys(errors)[0];
255
if (firstErrorField) {
256
document.querySelector(`[name="${firstErrorField}"]`)?.focus();
257
}
258
}
259
);
260
261
// Form reset actions
262
const resetToDefault = () => {
263
resetForm();
264
};
265
266
const resetWithCustomValues = () => {
267
resetForm({
268
values: {
269
name: 'Default Name',
270
email: '',
271
phone: ''
272
},
273
errors: {},
274
touched: {}
275
});
276
};
277
278
const forceReset = () => {
279
resetForm(undefined, { force: true });
280
};
281
282
// Submit count tracking
283
const submitAttempts = computed(() => {
284
const count = submitCount.value;
285
if (count === 0) return 'No attempts yet';
286
if (count === 1) return '1 attempt';
287
if (count > 5) return `${count} attempts (consider assistance)`;
288
return `${count} attempts`;
289
});
290
291
// Retry logic based on submit count
292
const handleRetrySubmit = () => {
293
const attempts = submitCount.value;
294
295
if (attempts >= 3) {
296
showConfirmDialog({
297
title: 'Multiple Failed Attempts',
298
message: 'You have tried to submit this form multiple times. Would you like to reset and start over?',
299
onConfirm: () => resetForm(),
300
onCancel: () => onSubmit()
301
});
302
} else {
303
onSubmit();
304
}
305
};
306
```
307
308
### State Mutation Actions
309
310
#### useSetFieldValue Composable
311
312
Programmatically set field value.
313
314
```typescript { .api }
315
/**
316
* Programmatically set field value
317
* @returns Function to set field value
318
*/
319
function useSetFieldValue(): (path: string, value: any, shouldValidate?: boolean) => void;
320
```
321
322
#### useSetFieldError Composable
323
324
Programmatically set field error.
325
326
```typescript { .api }
327
/**
328
* Programmatically set field error
329
* @returns Function to set field error
330
*/
331
function useSetFieldError(): (path: string, message: string | string[]) => void;
332
```
333
334
#### useSetFieldTouched Composable
335
336
Programmatically set field touched state.
337
338
```typescript { .api }
339
/**
340
* Programmatically set field touched state
341
* @returns Function to set field touched state
342
*/
343
function useSetFieldTouched(): (path: string, isTouched: boolean) => void;
344
```
345
346
#### useSetFormValues Composable
347
348
Programmatically set multiple field values.
349
350
```typescript { .api }
351
/**
352
* Programmatically set multiple field values
353
* @returns Function to set form values
354
*/
355
function useSetFormValues(): (values: Record<string, any>, shouldValidate?: boolean) => void;
356
```
357
358
#### useSetFormErrors Composable
359
360
Programmatically set multiple field errors.
361
362
```typescript { .api }
363
/**
364
* Programmatically set multiple field errors
365
* @returns Function to set form errors
366
*/
367
function useSetFormErrors(): (errors: Record<string, string | string[] | undefined>) => void;
368
```
369
370
#### useSetFormTouched Composable
371
372
Programmatically set multiple field touched states.
373
374
```typescript { .api }
375
/**
376
* Programmatically set multiple field touched states
377
* @returns Function to set form touched states
378
*/
379
function useSetFormTouched(): (touched: Record<string, boolean> | boolean) => void;
380
```
381
382
**State Mutation Examples:**
383
384
```typescript
385
import {
386
useSetFieldValue,
387
useSetFieldError,
388
useSetFieldTouched,
389
useSetFormValues,
390
useSetFormErrors,
391
useSetFormTouched,
392
useForm
393
} from "vee-validate";
394
395
const { handleSubmit } = useForm();
396
const setFieldValue = useSetFieldValue();
397
const setFieldError = useSetFieldError();
398
const setFieldTouched = useSetFieldTouched();
399
const setFormValues = useSetFormValues();
400
const setFormErrors = useSetFormErrors();
401
const setFormTouched = useSetFormTouched();
402
403
// Individual field mutations
404
const updateUserEmail = (email: string) => {
405
setFieldValue('email', email, true); // true = trigger validation
406
setFieldTouched('email', true);
407
};
408
409
const markEmailAsInvalid = (errorMessage: string) => {
410
setFieldError('email', errorMessage);
411
setFieldTouched('email', true);
412
};
413
414
// Bulk form mutations
415
const loadUserData = (userData: any) => {
416
setFormValues({
417
name: userData.name,
418
email: userData.email,
419
phone: userData.phone,
420
address: {
421
street: userData.address?.street || '',
422
city: userData.address?.city || '',
423
zipCode: userData.address?.zipCode || ''
424
}
425
}, false); // false = don't trigger validation
426
};
427
428
const handleServerValidationErrors = (serverErrors: Record<string, string>) => {
429
// Set multiple field errors from server response
430
setFormErrors(serverErrors);
431
432
// Mark all error fields as touched
433
const touchedState: Record<string, boolean> = {};
434
Object.keys(serverErrors).forEach(field => {
435
touchedState[field] = true;
436
});
437
setFormTouched(touchedState);
438
};
439
440
// Form state management patterns
441
const clearAllErrors = () => {
442
setFormErrors({});
443
};
444
445
const markAllFieldsAsTouched = () => {
446
setFormTouched(true); // true = mark all fields as touched
447
};
448
449
const resetFieldStates = () => {
450
setFormErrors({});
451
setFormTouched(false); // false = mark all fields as untouched
452
};
453
454
// Conditional field updates
455
const handleCountryChange = (country: string) => {
456
setFieldValue('country', country);
457
458
// Clear dependent fields when country changes
459
if (country === 'US') {
460
setFieldValue('state', '');
461
setFieldValue('zipCode', '');
462
} else {
463
setFieldValue('province', '');
464
setFieldValue('postalCode', '');
465
}
466
};
467
468
// Auto-save functionality
469
const autoSaveForm = debounce(async (values: any) => {
470
try {
471
await fetch('/api/autosave', {
472
method: 'POST',
473
body: JSON.stringify(values)
474
});
475
476
setFieldError('', ''); // Clear any previous save errors
477
} catch (error) {
478
setFieldError('', 'Auto-save failed');
479
}
480
}, 1000);
481
482
// Dynamic field validation
483
const validateFieldDependencies = async (changedField: string, value: any) => {
484
if (changedField === 'password') {
485
// Re-validate password confirmation when password changes
486
const confirmPassword = formValues.confirmPassword;
487
if (confirmPassword) {
488
if (confirmPassword !== value) {
489
setFieldError('confirmPassword', 'Passwords do not match');
490
} else {
491
setFieldError('confirmPassword', '');
492
}
493
}
494
}
495
496
if (changedField === 'email') {
497
// Check email availability
498
try {
499
const response = await fetch(`/api/check-email?email=${value}`);
500
const { available } = await response.json();
501
502
if (!available) {
503
setFieldError('email', 'This email is already taken');
504
} else {
505
setFieldError('email', '');
506
}
507
} catch (error) {
508
setFieldError('email', 'Unable to verify email availability');
509
}
510
}
511
};
512
```
513
514
## Integration Patterns
515
516
### API Integration
517
518
Handling server responses and API integration with form actions.
519
520
```typescript
521
import {
522
useSubmitForm,
523
useSetFormErrors,
524
useSetFormValues,
525
useResetForm
526
} from "vee-validate";
527
528
const submitForm = useSubmitForm();
529
const setFormErrors = useSetFormErrors();
530
const setFormValues = useSetFormValues();
531
const resetForm = useResetForm();
532
533
// API submission with error handling
534
const handleApiSubmission = submitForm(
535
async (values, { setFieldError }) => {
536
const response = await fetch('/api/users', {
537
method: 'POST',
538
headers: { 'Content-Type': 'application/json' },
539
body: JSON.stringify(values)
540
});
541
542
const result = await response.json();
543
544
if (!response.ok) {
545
// Handle different error types
546
if (response.status === 422) {
547
// Validation errors
548
setFormErrors(result.errors);
549
} else if (response.status === 409) {
550
// Conflict errors (e.g., duplicate email)
551
setFieldError('email', result.message);
552
} else {
553
// General errors
554
setFieldError('', 'Submission failed. Please try again.');
555
}
556
throw new Error('Submission failed');
557
}
558
559
// Success - reset form or redirect
560
resetForm();
561
return result;
562
}
563
);
564
565
// Load data from API
566
const loadUserData = async (userId: string) => {
567
try {
568
const response = await fetch(`/api/users/${userId}`);
569
const userData = await response.json();
570
571
setFormValues(userData, false); // Don't trigger validation on load
572
} catch (error) {
573
setFormErrors({ '': 'Failed to load user data' });
574
}
575
};
576
```
577
578
### Multi-step Form Actions
579
580
Managing complex multi-step forms with validation and state persistence.
581
582
```typescript
583
const currentStep = ref(0);
584
const steps = ['personal', 'contact', 'preferences', 'review'];
585
586
const validateForm = useValidateForm();
587
const setFormTouched = useSetFormTouched();
588
589
const goToNextStep = async () => {
590
// Validate current step
591
const currentStepFields = getFieldsForStep(currentStep.value);
592
const isStepValid = await validateStepFields(currentStepFields);
593
594
if (isStepValid) {
595
currentStep.value++;
596
saveStepProgress();
597
} else {
598
// Mark fields as touched to show errors
599
const touchedFields: Record<string, boolean> = {};
600
currentStepFields.forEach(field => {
601
touchedFields[field] = true;
602
});
603
setFormTouched(touchedFields);
604
}
605
};
606
607
const goToPreviousStep = () => {
608
if (currentStep.value > 0) {
609
currentStep.value--;
610
}
611
};
612
613
const validateStepFields = async (fields: string[]) => {
614
for (const field of fields) {
615
const result = await validateField(field);
616
if (!result.valid) {
617
return false;
618
}
619
}
620
return true;
621
};
622
623
const saveStepProgress = () => {
624
localStorage.setItem('formProgress', JSON.stringify({
625
step: currentStep.value,
626
values: formValues.value
627
}));
628
};
629
```