Painless forms for Vue.js with comprehensive validation, composition API, and component-based approaches.
—
Composables for programmatically controlling form behavior, validation, state mutations, and submission handling. These composables provide imperative APIs for complex form interactions and integrations.
Validates the entire form programmatically.
/**
* Validates entire form programmatically
* @returns Function to trigger form validation
*/
function useValidateForm(): (opts?: Partial<ValidationOptions>) => Promise<FormValidationResult>;
interface ValidationOptions {
mode: SchemaValidationMode;
warn: boolean;
}
type SchemaValidationMode = 'validated-only' | 'silent' | 'force';Validates a specific field programmatically.
/**
* Validates specific field programmatically
* @returns Function to trigger field validation
*/
function useValidateField(): <TPath extends Path<any>>(
path: TPath,
opts?: Partial<ValidationOptions>
) => Promise<ValidationResult>;Validation Action Examples:
import { useValidateForm, useValidateField, useForm } from "vee-validate";
const { handleSubmit } = useForm();
const validateForm = useValidateForm();
const validateField = useValidateField();
// Manual form validation
const checkFormValidity = async () => {
const result = await validateForm();
if (!result.valid) {
console.log('Form validation failed:', result.errors);
// Focus first invalid field
const firstErrorField = Object.keys(result.errors)[0];
if (firstErrorField) {
document.querySelector(`[name="${firstErrorField}"]`)?.focus();
}
} else {
console.log('Form is valid:', result.values);
}
return result;
};
// Validate specific field
const checkEmailValidity = async () => {
const result = await validateField('email');
if (!result.valid) {
console.log('Email validation failed:', result.errors);
}
return result;
};
// Validation with different modes
const validateWithMode = async () => {
// Only validate previously validated fields
const validatedOnly = await validateForm({ mode: 'validated-only' });
// Validate without updating field states
const silent = await validateForm({ mode: 'silent' });
// Force validate all fields
const force = await validateForm({ mode: 'force' });
return { validatedOnly, silent, force };
};
// Progressive validation
const progressiveValidation = async () => {
const steps = ['email', 'password', 'confirmPassword', 'profile.name'];
for (const step of steps) {
const result = await validateField(step);
if (!result.valid) {
console.log(`Validation failed at step: ${step}`, result.errors);
return { success: false, failedAt: step };
}
}
return { success: true };
};Creates form submission handler with validation and error handling.
/**
* Creates form submission handler
* @returns Function to create submission handler
*/
function useSubmitForm(): <TReturn = unknown>(
onSubmit?: SubmissionHandler<any, any, TReturn>,
onInvalidSubmit?: InvalidSubmissionHandler
) => (e?: Event) => Promise<TReturn | undefined>;
type SubmissionHandler<TInput extends GenericObject, TOutput = TInput, TReturn = unknown> = (
values: TOutput,
ctx: SubmissionContext<TInput>
) => TReturn;
interface SubmissionContext<TInput extends GenericObject> extends FormActions<TInput> {
evt?: Event;
controlledValues: Partial<TInput>;
}
type InvalidSubmissionHandler<TInput extends GenericObject = GenericObject, TOutput extends GenericObject = TInput> = (
ctx: InvalidSubmissionContext<TInput, TOutput>
) => void;Resets form to initial or specified state.
/**
* Resets form to initial or specified state
* @returns Function to reset form
*/
function useResetForm(): (state?: Partial<FormState>, opts?: Partial<ResetFormOpts>) => void;
interface ResetFormOpts {
force: boolean;
}Gets number of form submission attempts.
/**
* Gets number of form submission attempts
* @returns Computed ref to submit count
*/
function useSubmitCount(): ComputedRef<number>;Submission Action Examples:
import {
useSubmitForm,
useResetForm,
useSubmitCount,
useForm
} from "vee-validate";
const { handleSubmit } = useForm();
const submitForm = useSubmitForm();
const resetForm = useResetForm();
const submitCount = useSubmitCount();
// Basic form submission
const onSubmit = submitForm(
async (values) => {
console.log('Submitting form:', values);
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values)
});
if (!response.ok) {
throw new Error('Submission failed');
}
return response.json();
},
({ errors, results }) => {
console.log('Form validation failed:', errors);
console.log('Validation results:', results);
}
);
// Advanced submission with error handling
const onAdvancedSubmit = submitForm(
async (values, { setFieldError, setErrors, evt }) => {
try {
// Show loading state
const loadingToast = showToast('Submitting...');
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(values)
});
const result = await response.json();
if (!response.ok) {
// Handle server validation errors
if (result.fieldErrors) {
Object.entries(result.fieldErrors).forEach(([field, error]) => {
setFieldError(field, error as string);
});
} else {
setFieldError('', result.message || 'Submission failed');
}
hideToast(loadingToast);
return;
}
// Success
hideToast(loadingToast);
showToast('Form submitted successfully!');
return result;
} catch (error) {
console.error('Submission error:', error);
setFieldError('', 'Network error. Please try again.');
}
},
({ errors, values, evt }) => {
// Handle client-side validation failures
const errorCount = Object.keys(errors).length;
showToast(`Please fix ${errorCount} validation error(s)`);
// Focus first error field
const firstErrorField = Object.keys(errors)[0];
if (firstErrorField) {
document.querySelector(`[name="${firstErrorField}"]`)?.focus();
}
}
);
// Form reset actions
const resetToDefault = () => {
resetForm();
};
const resetWithCustomValues = () => {
resetForm({
values: {
name: 'Default Name',
email: '',
phone: ''
},
errors: {},
touched: {}
});
};
const forceReset = () => {
resetForm(undefined, { force: true });
};
// Submit count tracking
const submitAttempts = computed(() => {
const count = submitCount.value;
if (count === 0) return 'No attempts yet';
if (count === 1) return '1 attempt';
if (count > 5) return `${count} attempts (consider assistance)`;
return `${count} attempts`;
});
// Retry logic based on submit count
const handleRetrySubmit = () => {
const attempts = submitCount.value;
if (attempts >= 3) {
showConfirmDialog({
title: 'Multiple Failed Attempts',
message: 'You have tried to submit this form multiple times. Would you like to reset and start over?',
onConfirm: () => resetForm(),
onCancel: () => onSubmit()
});
} else {
onSubmit();
}
};Programmatically set field value.
/**
* Programmatically set field value
* @returns Function to set field value
*/
function useSetFieldValue(): (path: string, value: any, shouldValidate?: boolean) => void;Programmatically set field error.
/**
* Programmatically set field error
* @returns Function to set field error
*/
function useSetFieldError(): (path: string, message: string | string[]) => void;Programmatically set field touched state.
/**
* Programmatically set field touched state
* @returns Function to set field touched state
*/
function useSetFieldTouched(): (path: string, isTouched: boolean) => void;Programmatically set multiple field values.
/**
* Programmatically set multiple field values
* @returns Function to set form values
*/
function useSetFormValues(): (values: Record<string, any>, shouldValidate?: boolean) => void;Programmatically set multiple field errors.
/**
* Programmatically set multiple field errors
* @returns Function to set form errors
*/
function useSetFormErrors(): (errors: Record<string, string | string[] | undefined>) => void;Programmatically set multiple field touched states.
/**
* Programmatically set multiple field touched states
* @returns Function to set form touched states
*/
function useSetFormTouched(): (touched: Record<string, boolean> | boolean) => void;State Mutation Examples:
import {
useSetFieldValue,
useSetFieldError,
useSetFieldTouched,
useSetFormValues,
useSetFormErrors,
useSetFormTouched,
useForm
} from "vee-validate";
const { handleSubmit } = useForm();
const setFieldValue = useSetFieldValue();
const setFieldError = useSetFieldError();
const setFieldTouched = useSetFieldTouched();
const setFormValues = useSetFormValues();
const setFormErrors = useSetFormErrors();
const setFormTouched = useSetFormTouched();
// Individual field mutations
const updateUserEmail = (email: string) => {
setFieldValue('email', email, true); // true = trigger validation
setFieldTouched('email', true);
};
const markEmailAsInvalid = (errorMessage: string) => {
setFieldError('email', errorMessage);
setFieldTouched('email', true);
};
// Bulk form mutations
const loadUserData = (userData: any) => {
setFormValues({
name: userData.name,
email: userData.email,
phone: userData.phone,
address: {
street: userData.address?.street || '',
city: userData.address?.city || '',
zipCode: userData.address?.zipCode || ''
}
}, false); // false = don't trigger validation
};
const handleServerValidationErrors = (serverErrors: Record<string, string>) => {
// Set multiple field errors from server response
setFormErrors(serverErrors);
// Mark all error fields as touched
const touchedState: Record<string, boolean> = {};
Object.keys(serverErrors).forEach(field => {
touchedState[field] = true;
});
setFormTouched(touchedState);
};
// Form state management patterns
const clearAllErrors = () => {
setFormErrors({});
};
const markAllFieldsAsTouched = () => {
setFormTouched(true); // true = mark all fields as touched
};
const resetFieldStates = () => {
setFormErrors({});
setFormTouched(false); // false = mark all fields as untouched
};
// Conditional field updates
const handleCountryChange = (country: string) => {
setFieldValue('country', country);
// Clear dependent fields when country changes
if (country === 'US') {
setFieldValue('state', '');
setFieldValue('zipCode', '');
} else {
setFieldValue('province', '');
setFieldValue('postalCode', '');
}
};
// Auto-save functionality
const autoSaveForm = debounce(async (values: any) => {
try {
await fetch('/api/autosave', {
method: 'POST',
body: JSON.stringify(values)
});
setFieldError('', ''); // Clear any previous save errors
} catch (error) {
setFieldError('', 'Auto-save failed');
}
}, 1000);
// Dynamic field validation
const validateFieldDependencies = async (changedField: string, value: any) => {
if (changedField === 'password') {
// Re-validate password confirmation when password changes
const confirmPassword = formValues.confirmPassword;
if (confirmPassword) {
if (confirmPassword !== value) {
setFieldError('confirmPassword', 'Passwords do not match');
} else {
setFieldError('confirmPassword', '');
}
}
}
if (changedField === 'email') {
// Check email availability
try {
const response = await fetch(`/api/check-email?email=${value}`);
const { available } = await response.json();
if (!available) {
setFieldError('email', 'This email is already taken');
} else {
setFieldError('email', '');
}
} catch (error) {
setFieldError('email', 'Unable to verify email availability');
}
}
};Handling server responses and API integration with form actions.
import {
useSubmitForm,
useSetFormErrors,
useSetFormValues,
useResetForm
} from "vee-validate";
const submitForm = useSubmitForm();
const setFormErrors = useSetFormErrors();
const setFormValues = useSetFormValues();
const resetForm = useResetForm();
// API submission with error handling
const handleApiSubmission = submitForm(
async (values, { setFieldError }) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values)
});
const result = await response.json();
if (!response.ok) {
// Handle different error types
if (response.status === 422) {
// Validation errors
setFormErrors(result.errors);
} else if (response.status === 409) {
// Conflict errors (e.g., duplicate email)
setFieldError('email', result.message);
} else {
// General errors
setFieldError('', 'Submission failed. Please try again.');
}
throw new Error('Submission failed');
}
// Success - reset form or redirect
resetForm();
return result;
}
);
// Load data from API
const loadUserData = async (userId: string) => {
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setFormValues(userData, false); // Don't trigger validation on load
} catch (error) {
setFormErrors({ '': 'Failed to load user data' });
}
};Managing complex multi-step forms with validation and state persistence.
const currentStep = ref(0);
const steps = ['personal', 'contact', 'preferences', 'review'];
const validateForm = useValidateForm();
const setFormTouched = useSetFormTouched();
const goToNextStep = async () => {
// Validate current step
const currentStepFields = getFieldsForStep(currentStep.value);
const isStepValid = await validateStepFields(currentStepFields);
if (isStepValid) {
currentStep.value++;
saveStepProgress();
} else {
// Mark fields as touched to show errors
const touchedFields: Record<string, boolean> = {};
currentStepFields.forEach(field => {
touchedFields[field] = true;
});
setFormTouched(touchedFields);
}
};
const goToPreviousStep = () => {
if (currentStep.value > 0) {
currentStep.value--;
}
};
const validateStepFields = async (fields: string[]) => {
for (const field of fields) {
const result = await validateField(field);
if (!result.valid) {
return false;
}
}
return true;
};
const saveStepProgress = () => {
localStorage.setItem('formProgress', JSON.stringify({
step: currentStep.value,
values: formValues.value
}));
};Install with Tessl CLI
npx tessl i tessl/npm-vee-validate