JAVASCRIPT
Building a Reusable Form Validation Composable in Vue 3
Create a custom composable in Vue 3 to encapsulate form validation logic, making it reusable across different forms and input fields with ease.
// src/composables/useFormValidation.js
import { ref, reactive, computed } from 'vue';
export function useFormValidation(formFields) {
const errors = reactive({}); // Stores validation errors
const touched = reactive({}); // Tracks if a field has been interacted with
const validateField = (fieldName, value, rules) => {
errors[fieldName] = [];
for (const rule of rules) {
if (!rule.validator(value)) {
errors[fieldName].push(rule.message);
}
}
};
const validateAllFields = () => {
let isValid = true;
for (const fieldName in formFields) {
touched[fieldName] = true; // Mark all fields as touched for full validation visibility
validateField(fieldName, formFields[fieldName].value, formFields[fieldName].rules);
if (errors[fieldName].length > 0) {
isValid = false;
}
}
return isValid;
};
const getErrorMessage = (fieldName) => {
return touched[fieldName] && errors[fieldName]?.length > 0 ? errors[fieldName][0] : '';
};
const isFormValid = computed(() => {
return Object.values(errors).every(fieldErrors => fieldErrors.length === 0);
});
// Mark field as touched on blur/change to show error only when appropriate
const onFieldBlur = (fieldName) => {
touched[fieldName] = true;
};
return {
errors,
touched,
validateField,
validateAllFields,
getErrorMessage,
isFormValid,
onFieldBlur,
};
}
// src/components/RegistrationForm.vue
<template>
<form @submit.prevent="submitForm">
<div>
<label for="username">Username:</label>
<input
type="text"
id="username"
v-model="formData.username"
@blur="onFieldBlur('username')"
@input="validateField('username', formData.username, formSchema.username.rules)"
/>
<span class="error">{{ getErrorMessage('username') }}</span>
</div>
<div>
<label for="email">Email:</label>
<input
type="email"
id="email"
v-model="formData.email"
@blur="onFieldBlur('email')"
@input="validateField('email', formData.email, formSchema.email.rules)"
/>
<span class="error">{{ getErrorMessage('email') }}</span>
</div>
<button type="submit" :disabled="!isFormValid">Register</button>
</form>
</template>
<script setup>
import { reactive } from 'vue';
import { useFormValidation } from '@/composables/useFormValidation'; // Adjust path as needed
const formData = reactive({
username: '',
email: ''
});
const formSchema = {
username: {
// Value will be dynamically linked below
rules: [
{ validator: (value) => value.length >= 3, message: 'Username must be at least 3 characters.' },
{ validator: (value) => /^[a-zA-Z0-9]+$/.test(value), message: 'Username can only contain letters and numbers.' },
],
},
email: {
// Value will be dynamically linked below
rules: [
{ validator: (value) => !!value, message: 'Email is required.' },
{ validator: (value) => /.+@.+\..+/.test(value), message: 'Please enter a valid email address.' },
],
},
};
// A more direct way to link schema values to formData for reactive updates:
Object.keys(formSchema).forEach(fieldName => {
Object.defineProperty(formSchema[fieldName], 'value', {
get: () => formData[fieldName]
});
});
// Initialize the composable with the form schema
const { validateField, validateAllFields, getErrorMessage, isFormValid, onFieldBlur } = useFormValidation(formSchema);
const submitForm = () => {
if (validateAllFields()) {
alert('Form submitted successfully!
' + JSON.stringify(formData));
// Proceed with API call or further processing
} else {
alert('Please correct the form errors.');
}
};
</script>
<style scoped>
.error { color: red; font-size: 0.8em; }
div { margin-bottom: 10px; }
</style>
How it works: This snippet provides a flexible `useFormValidation` composable for Vue 3. It manages validation rules, errors, and 'touched' states for form fields. Components integrate it by defining a schema for their `reactive` form data. The composable provides functions to validate individual fields on input/blur and all fields on submission, returning error messages and a computed property `isFormValid` for button enabling.