CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vee-validate

Painless forms for Vue.js with comprehensive validation, composition API, and component-based approaches.

Pending
Overview
Eval results
Files

form-actions.mddocs/

Form Actions

Composables for programmatically controlling form behavior, validation, state mutations, and submission handling. These composables provide imperative APIs for complex form interactions and integrations.

Capabilities

Validation Actions

useValidateForm Composable

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';

useValidateField Composable

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 };
};

Submission Actions

useSubmitForm Composable

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;

useResetForm Composable

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;
}

useSubmitCount Composable

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();
  }
};

State Mutation Actions

useSetFieldValue Composable

Programmatically set field value.

/**
 * Programmatically set field value
 * @returns Function to set field value
 */
function useSetFieldValue(): (path: string, value: any, shouldValidate?: boolean) => void;

useSetFieldError Composable

Programmatically set field error.

/**
 * Programmatically set field error
 * @returns Function to set field error
 */
function useSetFieldError(): (path: string, message: string | string[]) => void;

useSetFieldTouched Composable

Programmatically set field touched state.

/**
 * Programmatically set field touched state
 * @returns Function to set field touched state
 */
function useSetFieldTouched(): (path: string, isTouched: boolean) => void;

useSetFormValues Composable

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;

useSetFormErrors Composable

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;

useSetFormTouched Composable

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');
    }
  }
};

Integration Patterns

API Integration

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' });
  }
};

Multi-step Form Actions

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

docs

configuration-rules.md

core-validation.md

field-management.md

form-actions.md

form-management.md

index.md

state-access.md

vue-components.md

tile.json