JAVASCRIPT
Custom Reactive Form Validation Composable in Vue 3
Build a flexible and reusable custom composable for reactive form input validation in Vue 3, providing immediate feedback to users and improving data quality.
// composables/useFieldValidation.js
import { ref, computed, watch } from 'vue';
export function useFieldValidation(initialValue, rules = []) {
const value = ref(initialValue);
const isTouched = ref(false);
const errors = computed(() => {
if (!isTouched.value) return [];
const validationErrors = [];
for (const rule of rules) {
const error = rule(value.value);
if (error) {
validationErrors.push(error);
}
}
return validationErrors;
});
const isValid = computed(() => errors.value.length === 0);
const errorMessage = computed(() => errors.value[0] || ''); // Display first error
const touch = () => {
isTouched.value = true;
};
// Watch for changes in value and re-evaluate validity
watch(value, () => {
if (isTouched.value) { // Only re-validate if already touched
// Trigger computed `errors` recalculation
}
}, { deep: true }); // Use deep: true if value can be an object
return {
value,
errors,
isValid,
errorMessage,
touch,
isTouched
};
}
// App.vue (or any component)
<template>
<div>
<h2>User Registration</h2>
<div>
<label for="username">Username:</label>
<input
type="text"
id="username"
v-model="usernameField.value"
@blur="usernameField.touch()"
/>
<p v-if="!usernameField.isValid && usernameField.isTouched" style="color: red;">
{{ usernameField.errorMessage }}
</p>
</div>
<div>
<label for="email">Email:</label>
<input
type="email"
id="email"
v-model="emailField.value"
@blur="emailField.touch()"
/>
<p v-if="!emailField.isValid && emailField.isTouched" style="color: red;">
{{ emailField.errorMessage }}
</p>
</div>
<button :disabled="!isFormValid">Submit</button>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useFieldValidation } from './composables/useFieldValidation';
// Define validation rules
const required = (val) => (val && val.trim() !== '' ? null : 'Field is required.');
const minLength = (len) => (val) => (val && val.length >= len ? null : `Min length is ${len}.`);
const isEmail = (val) => (/^\S+@\S+\.\S+$/.test(val) ? null : 'Must be a valid email.');
const usernameField = useFieldValidation('', [required, minLength(3)]);
const emailField = useFieldValidation('', [required, isEmail]);
const isFormValid = computed(() => usernameField.isValid.value && emailField.isValid.value);
</script>
How it works: This snippet demonstrates how to create a reusable `useFieldValidation` composable in Vue 3 for reactive form validation. It manages a field's value, tracks its `isTouched` state, and dynamically computes `errors` based on an array of provided validation `rules`. The component consuming this composable can bind `v-model` to `field.value`, call `field.touch()` on blur to activate validation feedback, and display `field.errorMessage`. This approach allows for highly modular and reactive validation logic across multiple form fields without relying on external libraries for basic use cases.