0
# Validation State and Control
1
2
Complete validation state management with reactive properties and control methods for tracking field state, errors, and programmatic validation control.
3
4
## Capabilities
5
6
### Validation State Properties
7
8
Reactive properties available on all validation objects for tracking validation status.
9
10
```javascript { .api }
11
interface ValidationState {
12
/**
13
* True if any validation rule is failing
14
*/
15
readonly $invalid: boolean;
16
17
/**
18
* True if all validation rules are passing (opposite of $invalid)
19
*/
20
readonly $valid: boolean;
21
22
/**
23
* True if any async validation is currently pending
24
*/
25
readonly $pending: boolean;
26
27
/**
28
* True if the field has been touched/modified by user interaction
29
*/
30
readonly $dirty: boolean;
31
32
/**
33
* True if this field or any nested field is dirty
34
*/
35
readonly $anyDirty: boolean;
36
37
/**
38
* True if field is invalid AND dirty (commonly used for error display)
39
*/
40
readonly $error: boolean;
41
42
/**
43
* True if this field or any nested field has an error
44
*/
45
readonly $anyError: boolean;
46
47
/**
48
* Parameter object from validators (contains metadata)
49
*/
50
readonly $params: object | null;
51
}
52
```
53
54
**Usage:**
55
56
```javascript
57
// Template usage
58
export default {
59
template: `
60
<div>
61
<input
62
v-model="email"
63
:class="{ 'error': $v.email.$error }"
64
@blur="$v.email.$touch()"
65
/>
66
67
<!-- Show error only when field is dirty and invalid -->
68
<div v-if="$v.email.$error" class="error-message">
69
<span v-if="!$v.email.required">Email is required</span>
70
<span v-if="!$v.email.email">Email format is invalid</span>
71
</div>
72
73
<!-- Show loading indicator for async validation -->
74
<div v-if="$v.email.$pending" class="loading">
75
Validating email...
76
</div>
77
78
<!-- Overall form state -->
79
<button
80
:disabled="$v.$invalid || $v.$pending"
81
@click="submitForm"
82
>
83
Submit
84
</button>
85
</div>
86
`,
87
88
computed: {
89
formIsReady() {
90
return !this.$v.$invalid && !this.$v.$pending
91
},
92
93
hasAnyErrors() {
94
return this.$v.$anyError
95
}
96
}
97
}
98
```
99
100
### Validation Control Methods
101
102
Methods for programmatically controlling validation state.
103
104
```javascript { .api }
105
interface ValidationControl {
106
/**
107
* Marks the field as dirty (touched by user)
108
* Triggers error display if field is invalid
109
*/
110
$touch(): void;
111
112
/**
113
* Resets the field to pristine state (not dirty)
114
* Hides error display even if field is invalid
115
*/
116
$reset(): void;
117
118
/**
119
* Flattens nested validation parameters into a flat array structure
120
* Useful for programmatic access to all validation metadata
121
*/
122
$flattenParams(): Array<{path: string[], name: string, params: object}>;
123
124
/**
125
* Getter/setter for the validated model value
126
* Setting triggers validation and marks field as dirty
127
*/
128
$model: any;
129
}
130
```
131
132
**Usage:**
133
134
```javascript
135
export default {
136
methods: {
137
// Touch all fields to show validation errors
138
validateAll() {
139
this.$v.$touch()
140
if (!this.$v.$invalid) {
141
this.submitForm()
142
}
143
},
144
145
// Reset specific field
146
resetEmail() {
147
this.$v.email.$reset()
148
this.email = ''
149
},
150
151
// Reset entire form
152
resetForm() {
153
this.$v.$reset()
154
this.email = ''
155
this.name = ''
156
this.password = ''
157
},
158
159
// Programmatically set and validate value
160
setEmailFromAPI(newEmail) {
161
// Setting $model triggers validation and marks as dirty
162
this.$v.email.$model = newEmail
163
},
164
165
// Get flattened parameter structure for debugging or error reporting
166
debugValidation() {
167
const flatParams = this.$v.$flattenParams()
168
console.log('All validation parameters:', flatParams)
169
170
// Example output:
171
// [
172
// { path: ['email'], name: 'required', params: { type: 'required' } },
173
// { path: ['email'], name: 'email', params: { type: 'email' } },
174
// { path: ['password'], name: 'minLength', params: { type: 'minLength', min: 8 } }
175
// ]
176
},
177
178
// Handle form submission
179
submitForm() {
180
// Touch all fields first
181
this.$v.$touch()
182
183
// Check if form is valid
184
if (this.$v.$invalid) {
185
console.log('Form has validation errors')
186
return
187
}
188
189
// Check for pending async validations
190
if (this.$v.$pending) {
191
console.log('Validation still in progress')
192
return
193
}
194
195
// Form is valid, proceed with submission
196
console.log('Submitting form...')
197
}
198
}
199
}
200
```
201
202
### Individual Validator State
203
204
Each validation rule exposes its own state for fine-grained control.
205
206
```javascript { .api }
207
interface ValidatorState {
208
/**
209
* True if this specific validator is passing
210
*/
211
readonly [validatorName]: boolean;
212
213
/**
214
* True if this specific validator is pending (async)
215
*/
216
readonly $pending: boolean;
217
218
/**
219
* Parameters for this specific validator
220
*/
221
readonly $params: object | null;
222
}
223
```
224
225
**Usage:**
226
227
```javascript
228
export default {
229
validations: {
230
password: {
231
required,
232
minLength: minLength(8),
233
strongPassword: customStrongPasswordValidator
234
}
235
},
236
237
computed: {
238
passwordErrors() {
239
const pw = this.$v.password
240
if (!pw.$dirty) return []
241
242
const errors = []
243
244
// Check each individual validator
245
if (!pw.required) {
246
errors.push('Password is required')
247
}
248
if (!pw.minLength) {
249
errors.push(`Password must be at least ${pw.minLength.$params.min} characters`)
250
}
251
if (!pw.strongPassword) {
252
errors.push('Password must include uppercase, lowercase, number, and special character')
253
}
254
255
return errors
256
},
257
258
passwordStrength() {
259
const pw = this.$v.password
260
if (!pw.$dirty || !this.password) return 0
261
262
let strength = 0
263
if (pw.required) strength += 1
264
if (pw.minLength) strength += 2
265
if (pw.strongPassword) strength += 2
266
267
return strength
268
}
269
}
270
}
271
```
272
273
## State Management Patterns
274
275
### Form Validation Workflows
276
277
Common patterns for form validation and user experience.
278
279
**Usage:**
280
281
```javascript
282
export default {
283
data() {
284
return {
285
formSubmitted: false,
286
showValidationSummary: false
287
}
288
},
289
290
computed: {
291
// Show errors after form submission or when field is dirty
292
shouldShowErrors() {
293
return (field) => {
294
return this.formSubmitted || field.$dirty
295
}
296
},
297
298
// Collect all validation errors
299
allErrors() {
300
const errors = []
301
302
const collectErrors = (validation, path = '') => {
303
for (const key in validation) {
304
if (key.startsWith('$')) continue
305
306
const field = validation[key]
307
const fieldPath = path ? `${path}.${key}` : key
308
309
if (typeof field === 'object' && field.$error) {
310
// Individual field error
311
for (const rule in field) {
312
if (rule.startsWith('$') || field[rule]) continue
313
errors.push({
314
field: fieldPath,
315
rule: rule,
316
message: this.getErrorMessage(fieldPath, rule, field[rule].$params)
317
})
318
}
319
} else if (typeof field === 'object') {
320
// Nested validation
321
collectErrors(field, fieldPath)
322
}
323
}
324
}
325
326
collectErrors(this.$v)
327
return errors
328
}
329
},
330
331
methods: {
332
async handleSubmit() {
333
this.formSubmitted = true
334
this.$v.$touch()
335
336
// Wait for any pending async validations
337
if (this.$v.$pending) {
338
await this.waitForValidation()
339
}
340
341
if (this.$v.$invalid) {
342
this.showValidationSummary = true
343
return
344
}
345
346
try {
347
await this.submitToAPI()
348
this.resetFormAfterSubmit()
349
} catch (error) {
350
console.error('Submission failed:', error)
351
}
352
},
353
354
waitForValidation() {
355
return new Promise((resolve) => {
356
const checkPending = () => {
357
if (!this.$v.$pending) {
358
resolve()
359
} else {
360
this.$nextTick(checkPending)
361
}
362
}
363
checkPending()
364
})
365
},
366
367
resetFormAfterSubmit() {
368
this.formSubmitted = false
369
this.showValidationSummary = false
370
this.$v.$reset()
371
372
// Reset form data
373
Object.assign(this.$data, this.$options.data())
374
},
375
376
getErrorMessage(field, rule, params) {
377
const messages = {
378
required: `${field} is required`,
379
email: `${field} must be a valid email`,
380
minLength: `${field} must be at least ${params?.min} characters`,
381
// ... add more message mappings
382
}
383
384
return messages[rule] || `${field} is invalid`
385
}
386
}
387
}
388
```
389
390
### Reactive Validation Watching
391
392
Advanced patterns for reacting to validation state changes.
393
394
**Usage:**
395
396
```javascript
397
export default {
398
watch: {
399
// Watch overall form validity
400
'$v.$invalid': {
401
handler(isInvalid) {
402
this.$emit('validity-changed', !isInvalid)
403
},
404
immediate: true
405
},
406
407
// Watch specific field for real-time feedback
408
'$v.email.$error': {
409
handler(hasError) {
410
if (hasError && this.$v.email.$dirty) {
411
this.showEmailHelp = true
412
}
413
}
414
},
415
416
// Watch for pending state changes
417
'$v.$pending'(isPending) {
418
this.isValidating = isPending
419
420
if (isPending) {
421
this.validationStartTime = Date.now()
422
} else {
423
console.log(`Validation completed in ${Date.now() - this.validationStartTime}ms`)
424
}
425
},
426
427
// Deep watch for nested validation changes
428
'$v': {
429
handler(newVal, oldVal) {
430
// Custom logic for validation state changes
431
this.saveValidationStateToLocalStorage()
432
},
433
deep: true
434
}
435
},
436
437
methods: {
438
saveValidationStateToLocalStorage() {
439
const state = {
440
dirty: this.$v.$dirty,
441
invalid: this.$v.$invalid,
442
errors: this.allErrors
443
}
444
localStorage.setItem('formValidationState', JSON.stringify(state))
445
}
446
}
447
}
448
```