Hone logo
Hone
Problems

Vue 3 Validation Composable

Build a reusable Vue 3 composable function in TypeScript to manage form input validation. This composable should provide a flexible way to define validation rules, track input validity, and display error messages, making form development more robust and maintainable.

Problem Description

Your task is to create a composable function, useValidation, that can be used within Vue 3 components to handle the validation of form inputs. This composable should be generic enough to work with various input types and validation rules.

Key Requirements:

  • Define Validation Rules: Allow users to define custom validation rules for each input field. These rules should be functions that take the input value and return either true (valid) or a string (error message).
  • Track Input Validity: The composable should maintain the validity state for each validated input.
  • Manage Error Messages: Store and expose the error message associated with each invalid input.
  • Reactivity: All validation states (validity, error messages) must be reactive.
  • Trigger Validation: Provide a mechanism to manually trigger validation for all fields or individual fields.
  • Reset State: Include a way to reset the validation state of all fields.

Expected Behavior:

  • When an input's value changes, its validation should be automatically re-evaluated (optional, but recommended).
  • The composable should expose an object or map containing the validation state for each field.
  • A function should be available to check if the entire form is valid.

Edge Cases to Consider:

  • Handling inputs that are initially empty.
  • Debouncing validation on input to prevent excessive checks.
  • Allowing for asynchronous validation rules (e.g., checking if an email already exists in a database).

Examples

Let's assume a simple form with two inputs: email and password.

Example 1: Basic Validation Setup

// Assume this is within a Vue component's setup function

import { ref } from 'vue';
import { useValidation } from './useValidation'; // Assuming your composable is here

const email = ref('');
const password = ref('');

const {
  fields,
  errors,
  isValid,
  validateField,
  validateAll,
  resetValidation,
} = useValidation({
  email: {
    value: email,
    rules: [
      (value: string) => value.trim() !== '' || 'Email is required',
      (value: string) => /\S+@\S+\.\S+/.test(value) || 'Invalid email format',
    ],
  },
  password: {
    value: password,
    rules: [
      (value: string) => value.trim() !== '' || 'Password is required',
      (value: string) => value.length >= 8 || 'Password must be at least 8 characters long',
    ],
  },
});

// In the template:
// <input type="email" v-model="email">
// <span v-if="errors.email">{{ errors.email }}</span>
// <button @click="validateAll" :disabled="!isValid">Submit</button>
// <button @click="resetValidation">Reset</button>

Output (Initial State):

fields: {
  email: { value: '', isTouched: false, isValid: false },
  password: { value: '', isTouched: false, isValid: false }
}
errors: {
  email: 'Email is required',
  password: 'Password is required'
}
isValid: false

Explanation: Initially, both fields are empty, and their respective "required" rules fail, populating the errors object and setting isValid to false.

Example 2: After User Interaction and Validation Trigger

  • User types test@example.com into the email input.
  • User types password123 into the password input.
  • User clicks the "Submit" button (which triggers validateAll).

Output (After Interaction and Validation):

fields: {
  email: { value: 'test@example.com', isTouched: true, isValid: true },
  password: { value: 'password123', isTouched: true, isValid: true }
}
errors: {
  email: null,
  password: null
}
isValid: true

Explanation: Both inputs now satisfy all their defined rules. The errors object for both fields is null (or empty), and isValid is true, enabling the submit button.

Example 3: Validation Error and Reset

  • User types invalid-email into the email input.
  • User types short into the password input.
  • User clicks the "Submit" button (which triggers validateAll).
  • User clicks the "Reset" button.

Output (After Validation Errors):

fields: {
  email: { value: 'invalid-email', isTouched: true, isValid: false },
  password: { value: 'short', isTouched: true, isValid: false }
}
errors: {
  email: 'Invalid email format',
  password: 'Password must be at least 8 characters long'
}
isValid: false

Output (After Reset):

fields: {
  email: { value: '', isTouched: false, isValid: false },
  password: { value: '', isTouched: false, isValid: false }
}
errors: {
  email: 'Email is required',
  password: 'Password is required'
}
isValid: false

Explanation: The validation correctly identifies the errors. After clicking reset, the fields and errors states revert to their initial values.

Constraints

  • The composable must be implemented in TypeScript.
  • It should leverage Vue 3's Composition API.
  • The validation rules should be functions that return true for valid or a string for an error message.
  • The composable should be designed to be reusable across multiple components.
  • Avoid using external validation libraries (e.g., Vuelidate, Yup) for the core implementation.

Notes

  • Consider how you will handle the initial state of validation – should fields be validated on mount, or only after the user interacts with them (isTouched state)?
  • Think about how to structure the returned state from the composable for easy consumption in Vue templates.
  • The value for each field passed into the composable should likely be a Ref to allow for reactivity.
  • For asynchronous validation, consider how the composable will indicate a pending validation state.
Loading editor...
typescript