or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration-rules.mdcore-validation.mdfield-management.mdform-actions.mdform-management.mdindex.mdstate-access.mdvue-components.md

form-actions.mddocs/

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

```