or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collections.mdcustom-validators.mdindex.mdintegration.mdvalidation-state.mdvalidators.md

validation-state.mddocs/

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

```