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 astring(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.cominto the email input. - User types
password123into 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-emailinto the email input. - User types
shortinto 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
truefor valid or astringfor 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 (
isTouchedstate)? - Think about how to structure the returned state from the composable for easy consumption in Vue templates.
- The
valuefor each field passed into the composable should likely be aRefto allow for reactivity. - For asynchronous validation, consider how the composable will indicate a pending validation state.