Skip to content

useFormValidator

useFormValidator and useFormField are two Vue 3 composables designed to simplify form validation using Valibot as the validation library. These composables offer a flexible and typed approach to handle form validation in your Vue 3 applications.

Prerequisites

To use this composable, you have to install the Valibot dependency

valibot
bash
npm install valibot

Introduction

Best Practices
  1. Use typed Valibot schemas to ensure type consistency.
  2. Choose the appropriate validation mode based on your form's needs.
  3. Use useFormField for fine-grained management of each form field.
  4. Use the handleSubmit returned by useFormValidator to handle form submission securely.
  5. Leverage computed values like isValid, hasError, errorMessage, and others to control your user interface state.
Validation modes details
  • lazy: (default) Validates only on value changes
  • aggressive: Validates all fields immediately and on every change
  • eager: (recommended) Validates on initial blur (if not empty), then on every change (requires useFormField to add validation events)
  • blur: Validates only on focus loss (requires useFormField to add validation events)
  • progressive: Validates the field at each user interaction (value change or blur). The field becomes valid after the first successful validation and then validated on input value change. If the field is invalid, the error message on the first blur event (requires useFormField to add validation events)
How to get a typed model from schema?

To get a typed model from the Valibot schema, you can use the InferFormValidatorSchema helper.

ts
import { ref } from 'vue'
import { pipe, string, nonEmpty, number, minValue, maxValue, minLength } from 'valibot'
import { InferFormValidatorSchema } from 'maz-ui'

const schema = ref({
  name: pipe(string('Name is required'), nonEmpty('Name is required'), minLength(3, 'Name must be at least 3 characters')),
  age: pipe(number('Age is required'), minValue(18, 'Age must be greater than 18'), maxValue(100, 'Age must be less than 100')),
  country: pipe(string('Country is required'), nonEmpty('Country is required')),
})

/**
 * { name: string, age: number, country: string }
 */
type Model = InferFormValidatorSchema<typeof schema>

const { model } = useFormValidator<Model>({
  schema,
})
How to bind validation events with useFormField for eager, blur, or progressive modes?

To use the eager, blur, or progressive validation modes, you must use the useFormField composable to add the necessary validation events.

3 ways to bind validation events:

1. Use the ref attribute on the component to get the reference

You can use the ref attribute on the component and pass the reference to the useFormField composable.

This method will search HTML elements (input, select, and textarea) into the component and add the necessary validation events.

vue
<template>
  <MazInput
    ref="inputRef"
    v-model="value"
    :hint="errorMessage"
    :error="hasError"
    :success="isValid"
  />
  <!-- Work with HTML input -->
  <input ref="inputRef" v-model="value" />
</template>

<script setup lang="ts">
import { useFormField } from 'maz-ui'

const { value, errorMessage, isValid, hasError } = useFormField('name', {
  ref: 'inputRef',
})
</script>
2. Use the v-bind directive to bind the validation events

You can use the v-bind directive to bind the validation events to the component or HTML element.

If you use this method with a custom component, the component must emit the blur event to trigger the field validation. Otherwise, use the first method.

vue
<template>
  <MazInput
    v-model="value"
    :hint="errorMessage"
    :error="hasError"
    :success="isValid"
    v-bind="validationEvents"
  />
  <!-- or -->
  <input v-model="value" v-bind="validationEvents" />
</template>

##### 3. Use the `onBlur` handler directly from `useFormField`

This method works if the component emits the `blur` event. Otherwise, use the first method.

```vue{7}
<template>
  <MazInput
    v-model="value"
    :hint="errorMessage"
    :error="hasError"
    :success="isValid"
    @blur="onBlur"
  />
</template>

<script setup lang="ts">
import { useFormField } from 'maz-ui'

const { value, errorMessage, isValid, hasError, onBlur } = useFormField('name')
</script>

Basic Usage with lazy mode

In this example, we will create a simple form with four fields: name, age, agree and country. The form will be validated in lazy mode, which means that the fields will be validated on every change.

TIP

Submit the form to show the validation errors

Form State
{
  "isValid": false,
  "isSubmitting": false,
  "isDirty": false,
  "isSubmitted": false,
  "errorMessages": {}
}

Enter your name (min 3 characters)
Enter your age (18-100)
Select your nationality (required)

Usage with useFormField

In this example, we will use the useFormField composable to handle the validation of each field individually.

Eager mode

With eager mode, each form field is validated on blur (if not empty) and then on every change. This mode is made for a better user experience, as the user will see the validation errors only after they have finished typing.

Enter your name
Enter your age
Select your nationality

Progressive mode

With progressive mode, the field becomes valid after the first successful validation and then validated on input value change. If the field is invalid, the error message is shown on the first blur event.

Enter your name
Enter your age
Select your nationality

Throlling and Debouncing

You can use the throttledFields and debouncedFields options to throttle or debounce the validation of specific fields.

The fields are validated with throttling or debouncing to avoid spamming the server or to wait for the user to finish typing before validating.

You can set the throttle or debounce time in milliseconds or use true for the default throttle time (1000ms) or debounce time (300ms).

Enter your name
Enter your age

useFormValidator

useFormValidator is the main composable for initializing form validation.

It accepts a validation schema, default values, and configuration options to handle form validation. You can also provide a model reference to bind the form data.

Parameters (FormValidatorOptions)

useFormValidator accepts an object with the following properties:

  • schema: MaybeRef<FormSchema<Model>> - The validation schema for the form.
  • model: Ref<Model> (optional) - A reference to the form's data model.
  • defaultValues: Partial<Model> (optional) - Default values for the form fields.
  • options: FormValidatorOptions (optional) - Configuration options for the form validation behavior.
    • mode: 'eager' | 'lazy' | 'aggressive' | 'blur' (optional) - Form validation mode. (default: 'lazy') - To use the eager or blur validation modes, you must use the useFormField composable to add the necessary validation events. - see validation modes
    • throttledFields: Partial<Record<ModelKey, number | true>> (optional) - Fields to validate with throttling. It's an object where the key is the field name and the value is the throttle time in milliseconds or true for the default throttle time (1000ms).
    • debouncedFields: Partial<Record<ModelKey, number | true>> (optional) - Fields to validate with debouncing. It's an object where the key is the field name and the value is the debounce time in milliseconds or true for the default debounce time (300ms).
    • scrollToError: string | false (optional) - Disable or provide a CSS selector for scrolling to errors (default '.has-field-error')
    • identifier: string | symbol (optional) - Identifier for the form (useful when you have multiple forms on the same component)

Return

useFormValidator returns an object containing:

  • isDirty: ComputedRef<boolean> - Indicates if the form has been modified.
  • isSubmitting: Ref<boolean> - Indicates if the form is currently being submitted.
  • isSubmitted: Ref<boolean> - Indicates if the form has been submitted.
  • isValid: ComputedRef<boolean> - Indicates if the form is valid.
  • errors: ComputedRef<Record<ModelKey, ValidationIssues>> - Validation errors for each field.
  • errorsMessages: ComputedRef<Record<string, string>> - The first validation error message for each field.
  • model: Ref<Model> - The form's data model.
  • fieldsStates: FieldsStates - The validation state of each field.
  • validateForm: (showErrors?: boolean) => Promise<boolean> - Function to validate the entire form.
  • scrollToError: (selector?: string, options?: { offset?: number }) => void - Function to scroll to the first field with an error.
  • handleSubmit: successCallback: (model: Model) => Promise<unknown> | unknown, scrollToError?: false | string - Form submission handler, the callback is called if the form is valid and passes the complete payload as an argument. The second argument is optional and can be used to disable or provide a CSS selector for scrolling to errors (default '.has-field-error').

useFormField

WARNING

Before using useFormField, make sure you have initialized the form with useFormValidator.

useFormField is a composable for handling validation at the individual form field level.

Useful for fine-grained control over form fields, useFormField provides computed properties for validation state, error messages, and more. Can be very useful when you are using fields in child components of form.

To use the modes eager, progressive or blur, you must use this useFormField composable to add the necessary validation events.

Parameters (FormFieldOptions)

  • name: ModelKey - The name of the field in the validation schema.
  • options: FormFieldOptions<T> (optional) - Field-specific options.
    • defaultValue: T (optional) - The default value of the field.
    • mode: 'eager' | 'lazy' | 'aggressive' | 'blur' | 'progressive' (optional) - The validation mode for the field - see validation modes
    • ref: string (optional) - Reference to the component to associate and trigger validation events on HTML Form elements (input, select and textarea) - not necessary for lazy, aggressive validation modes
    • formIdentifier: string | symbol (optional) - Identifier for the form (useful when you have multiple forms on the same component)

Return

useFormField returns an object containing:

  • errors: ComputedRef<ValidationIssues> - Validation errors for this field.
  • errorMessage: ComputedRef<string> - The first validation error message.
  • isValid: ComputedRef<boolean> - Indicates if the field is valid.
  • isDirty: ComputedRef<boolean> - Indicates if the field has been modified.
  • isBlurred: ComputedRef<boolean> - Indicates if the field has lost focus.
  • hasError: ComputedRef<boolean> - Indicates if the field has errors.
  • isValidated: ComputedRef<boolean> - Indicates if the field has been validated.
  • isValidating: ComputedRef<boolean> - Indicates if the field is currently being validated.
  • mode: ComputedRef<StrictOptions['mode']> - The validation mode for the field.
  • value: ComputedRef<T> - The value of the field.
  • validationEvents: ComputedRef<{ onBlur?: () => void; }> - Validation events to bind to the field. They are used to trigger field validation, to be used like this v-bind="validationEvents" (components must emit blur event to trigger field validation) - Not necessary for lazy, aggressive validation modes or if you use the component reference when initializing the composable.

Types

FormValidatorOptions

ts
type FormValidatorOptions = {
  /**
   * Validation mode
   * - lazy: validate on input value change
   * - aggressive: validate all fields immediately on form creation and on input value change
   * - blur: validate on blur
   * - eager: validate on blur at first (only if the field is not empty) and then on input value change
   * - progressive: The field becomes valid after the first successful validation and then validated on input value change. If the field is invalid, the error message on the first blur event.
   * @default 'lazy'
   */
  mode?: 'eager' | 'lazy' | 'aggressive' | 'blur' | 'progressive'
  /**
   * Fields to validate with throttling
   * Useful for fields that require a network request to avoid spamming the server
   * @example { name: 1000 } or { name: true } for the default throttle time (1000ms)
   */
  throttledFields?: Partial<Record<ModelKey, number | true>>
  /**
   * Fields to validate with debouncing
   * Useful to wait for the user to finish typing before validating
   * Useful for fields that require a network request to avoid spamming the server
   * @example { name: 300 } or { name: true } for the default debounce time (300ms)
   */
  debouncedFields?: Partial<Record<ModelKey, number | true>>
  /**
   * Scroll to the first error found
   * @default '.has-field-error'
   */
  scrollToError?: string | false
  /**
   * Identifier to use for the form
   * Useful to have multiple forms on the same page
   * @default `main-form-validator`
   */
  identifier?: string | symbol
}

FormFieldOptions

ts
type FormFieldOptions<T> = {
  /**
   * Default value of the field
   * @default undefined
   */
  defaultValue?: T
  /**
   * Validation mode
   * To override the form validation mode
   */
  mode?: 'eager' | 'lazy' | 'aggressive' | 'blur' | 'progressive'
  /**
   * Reference to the component or HTML element to associate and trigger validation events
   * Necessary for 'eager', 'progressive' and 'blur' validation modes
   */
  ref?: string
  /**
   * Identifier for the form
   * Useful when you have multiple forms on the same component
   * Should be the same as the one used in `useFormValidator`
   */
  formIdentifier?: string | symbol
}