0
# Form Validation
1
2
Async form validation using async-validator with reactive validation state and declarative component support.
3
4
## Capabilities
5
6
### useAsyncValidator
7
8
Reactive form validation with async-validator integration and detailed error reporting.
9
10
```typescript { .api }
11
/**
12
* Reactive form validation with async-validator integration
13
* @param value - Form data object to validate
14
* @param rules - Validation rules object
15
* @param options - Validation configuration options
16
* @returns Validation state and control methods
17
*/
18
function useAsyncValidator(
19
value: MaybeRefOrGetter<Record<string, any>>,
20
rules: MaybeRefOrGetter<Rules>,
21
options?: UseAsyncValidatorOptions
22
): UseAsyncValidatorReturn & PromiseLike<UseAsyncValidatorReturn>;
23
24
interface UseAsyncValidatorReturn {
25
/** Whether all validations pass */
26
pass: ShallowRef<boolean>;
27
/** Whether validation has completed */
28
isFinished: ShallowRef<boolean>;
29
/** Array of validation errors */
30
errors: ComputedRef<AsyncValidatorError['errors'] | undefined>;
31
/** Complete error information object */
32
errorInfo: ShallowRef<AsyncValidatorError | null>;
33
/** Errors organized by field name */
34
errorFields: ComputedRef<AsyncValidatorError['fields'] | undefined>;
35
/** Execute validation manually */
36
execute: () => Promise<UseAsyncValidatorExecuteReturn>;
37
}
38
39
interface UseAsyncValidatorExecuteReturn {
40
/** Whether validation passed */
41
pass: boolean;
42
/** Array of validation errors */
43
errors: AsyncValidatorError['errors'] | undefined;
44
/** Complete error information */
45
errorInfo: AsyncValidatorError | null;
46
/** Errors organized by field */
47
errorFields: AsyncValidatorError['fields'] | undefined;
48
}
49
50
interface UseAsyncValidatorOptions {
51
/** Options passed to async-validator */
52
validateOption?: ValidateOption;
53
/** Execute validation immediately */
54
immediate?: boolean; // default: true
55
/** Manual validation mode (disable auto-validation) */
56
manual?: boolean;
57
}
58
59
type AsyncValidatorError = Error & {
60
errors: ValidateError[];
61
fields: Record<string, ValidateError[]>;
62
};
63
64
// async-validator types
65
interface Rules {
66
[key: string]: RuleItem | RuleItem[];
67
}
68
69
interface RuleItem {
70
type?: RuleType;
71
required?: boolean;
72
pattern?: RegExp | string;
73
min?: number;
74
max?: number;
75
len?: number;
76
enum?: Array<string | number | boolean | null | undefined>;
77
whitespace?: boolean;
78
fields?: Rules;
79
defaultField?: RuleItem;
80
transform?: (value: any) => any;
81
message?: string | ((a?: string) => string);
82
asyncValidator?: (rule: InternalRuleItem, value: any, callback: (error?: string | Error) => void, source: Values, options: ValidateOption) => void;
83
validator?: (rule: InternalRuleItem, value: any, callback: (error?: string | Error) => void, source: Values, options: ValidateOption) => void;
84
}
85
86
type RuleType = 'string' | 'number' | 'boolean' | 'method' | 'regexp' | 'integer' | 'float' | 'array' | 'object' | 'enum' | 'date' | 'url' | 'hex' | 'email' | 'any';
87
88
interface ValidateError {
89
message?: string;
90
fieldValue?: any;
91
field?: string;
92
}
93
94
interface ValidateOption {
95
suppressWarning?: boolean;
96
first?: boolean;
97
firstFields?: boolean | string[];
98
}
99
```
100
101
**Usage Examples:**
102
103
```typescript
104
import { useAsyncValidator } from "@vueuse/integrations/useAsyncValidator";
105
import { ref, reactive } from 'vue';
106
107
// Basic form validation
108
const form = reactive({
109
name: '',
110
email: '',
111
age: 0
112
});
113
114
const rules = {
115
name: { type: 'string', required: true, min: 2 },
116
email: { type: 'email', required: true },
117
age: { type: 'number', required: true, min: 18 }
118
};
119
120
const { pass, errors, errorFields, execute } = useAsyncValidator(form, rules);
121
122
// Check validation state
123
watchEffect(() => {
124
if (pass.value) {
125
console.log('Form is valid!');
126
} else {
127
console.log('Validation errors:', errors.value);
128
}
129
});
130
131
// Manual validation
132
const handleSubmit = async () => {
133
const result = await execute();
134
if (result.pass) {
135
// Submit form
136
console.log('Submitting:', form);
137
} else {
138
console.log('Validation failed:', result.errors);
139
}
140
};
141
142
// Advanced validation with custom rules
143
const advancedRules = {
144
username: [
145
{ required: true, message: 'Username is required' },
146
{ min: 3, max: 20, message: 'Username must be 3-20 characters' },
147
{ pattern: /^[a-zA-Z0-9_]+$/, message: 'Username can only contain letters, numbers, and underscores' }
148
],
149
password: {
150
required: true,
151
validator: (rule, value, callback) => {
152
if (value.length < 8) {
153
callback(new Error('Password must be at least 8 characters'));
154
} else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
155
callback(new Error('Password must contain uppercase, lowercase, and numbers'));
156
} else {
157
callback();
158
}
159
}
160
},
161
confirmPassword: {
162
required: true,
163
validator: (rule, value, callback) => {
164
if (value !== form.password) {
165
callback(new Error('Passwords do not match'));
166
} else {
167
callback();
168
}
169
}
170
}
171
};
172
173
// Async validation
174
const asyncRules = {
175
email: {
176
required: true,
177
type: 'email',
178
asyncValidator: async (rule, value, callback) => {
179
try {
180
const response = await fetch(`/api/check-email?email=${value}`);
181
const result = await response.json();
182
if (!result.available) {
183
callback(new Error('Email is already taken'));
184
} else {
185
callback();
186
}
187
} catch (error) {
188
callback(new Error('Failed to validate email'));
189
}
190
}
191
}
192
};
193
194
// Manual validation mode
195
const { pass, execute } = useAsyncValidator(form, rules, {
196
immediate: false,
197
manual: true
198
});
199
200
// Display field-specific errors
201
const getFieldError = (fieldName: string) => {
202
return errorFields.value?.[fieldName]?.[0]?.message;
203
};
204
```
205
206
### UseAsyncValidator Component
207
208
Declarative validation component for template-based usage.
209
210
```typescript { .api }
211
/**
212
* Declarative validation component
213
*/
214
const UseAsyncValidator = defineComponent({
215
name: 'UseAsyncValidator',
216
props: {
217
/** Form data object to validate */
218
form: {
219
type: Object as PropType<Record<string, any>>,
220
required: true
221
},
222
/** Validation rules object */
223
rules: {
224
type: Object as PropType<Rules>,
225
required: true
226
},
227
/** Validation options */
228
options: {
229
type: Object as PropType<UseAsyncValidatorOptions>,
230
default: () => ({})
231
}
232
},
233
slots: {
234
default: (props: {
235
pass: boolean;
236
errors: AsyncValidatorError['errors'] | undefined;
237
errorFields: AsyncValidatorError['fields'] | undefined;
238
isFinished: boolean;
239
execute: () => Promise<UseAsyncValidatorExecuteReturn>;
240
}) => any;
241
}
242
});
243
```
244
245
**Component Usage:**
246
247
```vue
248
<template>
249
<UseAsyncValidator :form="form" :rules="rules" v-slot="{ pass, errors, errorFields, execute }">
250
<form @submit.prevent="execute">
251
<div>
252
<label>Name:</label>
253
<input v-model="form.name" />
254
<span v-if="errorFields?.name" class="error">
255
{{ errorFields.name[0].message }}
256
</span>
257
</div>
258
259
<div>
260
<label>Email:</label>
261
<input v-model="form.email" type="email" />
262
<span v-if="errorFields?.email" class="error">
263
{{ errorFields.email[0].message }}
264
</span>
265
</div>
266
267
<button type="submit" :disabled="!pass">
268
Submit
269
</button>
270
271
<div v-if="errors">
272
<h4>Validation Errors:</h4>
273
<ul>
274
<li v-for="error in errors" :key="error.field">
275
{{ error.message }}
276
</li>
277
</ul>
278
</div>
279
</form>
280
</UseAsyncValidator>
281
</template>
282
283
<script setup>
284
import { UseAsyncValidator } from "@vueuse/integrations/useAsyncValidator";
285
import { reactive } from 'vue';
286
287
const form = reactive({
288
name: '',
289
email: ''
290
});
291
292
const rules = {
293
name: { required: true, min: 2 },
294
email: { required: true, type: 'email' }
295
};
296
</script>
297
```