form.errors
Quick Start (30 seconds)
const { v } = Forms;
// Create form with validators
const form = Forms.create(
{ email: '', password: '' },
{
validators: {
email: v.email('Invalid email'),
password: v.minLength(8, 'Min 8 characters')
}
}
);
// Set invalid value
form.setValue('email', 'notanemail');
// Check errors
console.log(form.errors);
// { email: 'Invalid email' }
console.log(form.errors.email);
// 'Invalid email'
// Fix it
form.setValue('email', 'user@example.com');
console.log(form.errors.email);
// undefined ✅ (no error)What just happened? form.errors stores validation error messages. When validation fails, the error appears here. When it passes, the error is removed!
What is form.errors?
form.errors is a reactive object property that stores validation error messages for each form field.
Simply put, when a field's validation fails, the error message goes into form.errors. When validation passes, the error is removed.
Think of form.errors as a warning board that displays what's wrong with your form. When everything is valid, the board is empty!
Syntax
Reading Errors
// Read all errors
const allErrors = form.errors;
// Read specific field error
const emailError = form.errors.email;
const passwordError = form.errors.password;
// Check if field has error
if (form.errors.username) {
console.log('Username has error:', form.errors.username);
}Setting Errors Manually
// Set a single error
form.setError('email', 'This email is already taken');
// Clear a single error
form.clearError('email');
// Set multiple errors
form.setErrors({
email: 'Invalid email',
password: 'Too weak'
});
// Clear all errors
form.clearErrors();Type: Object (read/write via methods)
Structure: { fieldName: errorMessage, ... }
Why Does This Exist?
The Problem Without Centralized Error Tracking
Managing validation errors manually is messy:
// Manual error management (chaos!)
let emailError = null;
let passwordError = null;
let usernameError = null;
function validateEmail(value) {
if (!value) {
emailError = 'Email required';
} else if (!value.includes('@')) {
emailError = 'Invalid email';
} else {
emailError = null;
}
updateUIErrors();
}
function validatePassword(value) {
if (!value) {
passwordError = 'Password required';
} else if (value.length < 8) {
passwordError = 'Min 8 characters';
} else {
passwordError = null;
}
updateUIErrors();
}
// Check if form is valid
function isFormValid() {
return !emailError && !passwordError && !usernameError;
}
// Get all errors
function getAllErrors() {
const errors = {};
if (emailError) errors.email = emailError;
if (passwordError) errors.password = passwordError;
if (usernameError) errors.username = usernameError;
return errors;
}What's the Real Issue?
Scattered Error Variables
↓
Manual Error Tracking
↓
Hard to Check Overall Validity
↓
Repetitive Code
↓
Easy to Forget FieldsProblems: ❌ Separate variable for each field's error ❌ Manual validity checking ❌ Hard to display all errors ❌ Easy to forget to clear errors ❌ Repetitive error management code
The Solution with form.errors
const { v } = Forms;
const form = Forms.create(
{ email: '', password: '', username: '' },
{
validators: {
email: v.combine(v.required('Email required'), v.email('Invalid email')),
password: v.combine(v.required('Password required'), v.minLength(8, 'Min 8 chars')),
username: v.combine(v.required('Username required'), v.minLength(3, 'Min 3 chars'))
}
}
);
// Set values - errors update automatically!
form.setValue('email', 'invalid');
// form.errors.email = 'Invalid email'
form.setValue('password', 'short');
// form.errors.password = 'Min 8 chars'
// Check all errors at once
console.log(form.errors);
// { email: 'Invalid email', password: 'Min 8 chars' }
// Check if valid (automatic!)
console.log(form.isValid); // false
// Fix the issues
form.setValue('email', 'user@example.com');
form.setValue('password', 'secret123');
console.log(form.errors);
// {} (empty - no errors!)
console.log(form.isValid); // true ✅Benefits: ✅ All errors in one object ✅ Automatic error updates on validation ✅ Easy to check overall validity ✅ Easy to display all error messages ✅ Errors cleared automatically when fixed
Mental Model
Think of form.errors like a teacher's correction sheet:
Without form.errors (Sticky Notes)
Student Paper 1: "Missing comma" [sticky note]
Student Paper 2: "Wrong answer" [sticky note]
Student Paper 3: "Incomplete" [sticky note]
Teacher: "How many errors total?"
*Counts sticky notes manually*
"Are there any errors left?"
*Checks each paper again*With form.errors (Grading Sheet)
┌─────────────────────────────────┐
│ Grading Sheet │
├─────────────────────────────────┤
│ Paper 1: Missing comma │
│ Paper 2: Wrong answer │
│ Paper 3: Incomplete │
├─────────────────────────────────┤
│ Total Errors: 3 │
│ Status: Needs Revision │
└─────────────────────────────────┘
Teacher: "How many errors?" → Look at sheet: 3
"Any errors left?" → Sheet empty? NoKey Insight: form.errors collects all validation problems in one organized location for easy inspection and management.
How Does It Work?
Automatic Error Updates
User calls form.setValue('email', 'invalid')
↓
Triggers validator
↓
Validator returns 'Invalid email'
↓
form.errors.email = 'Invalid email'
↓
form.isValid = false
↓
UI updates automatically (if bound)Error Lifecycle
Initial State
form.errors = {}
↓ setValue with invalid data
Validation Fails
form.errors = { field: 'Error message' }
↓ setValue with valid data
Validation Passes
form.errors = {} (error removed)Visual Structure
form
├── values
│ ├── email: 'invalid'
│ └── password: 'short'
├── errors ← form.errors
│ ├── email: 'Invalid email'
│ └── password: 'Min 8 chars'
└── isValid: false (computed from errors)Basic Usage
Example 1: Checking for Errors
const { v } = Forms;
const form = Forms.create(
{ email: '' },
{
validators: {
email: v.email('Invalid email format')
}
}
);
// Invalid input
form.setValue('email', 'notanemail');
// Check if field has error
if (form.errors.email) {
console.log('Error:', form.errors.email);
// "Error: Invalid email format"
}
// Check all errors
console.log(Object.keys(form.errors).length > 0);
// true (has errors)Example 2: Displaying Error Messages
const { v } = Forms;
const form = Forms.create(
{ username: '', email: '', password: '' },
{
validators: {
username: v.required('Username required'),
email: v.email('Invalid email'),
password: v.minLength(8, 'Min 8 characters')
}
}
);
// Submit with empty fields
form.submit();
// Display all errors
Object.entries(form.errors).forEach(([field, error]) => {
console.log(`${field}: ${error}`);
});
// username: Username required
// email: Invalid email
// password: Min 8 charactersExample 3: Conditional Error Display
const { v } = Forms;
const form = Forms.create(
{ email: '' },
{
validators: {
email: v.combine(
v.required('Email required'),
v.email('Invalid email')
)
}
}
);
// Show error only if field is touched
function shouldShowError(field) {
return form.touched[field] && form.errors[field];
}
form.setValue('email', 'invalid');
if (shouldShowError('email')) {
console.log('Show error:', form.errors.email);
// "Show error: Invalid email"
}Example 4: Error Count
const { v } = Forms;
const form = Forms.create(
{ email: '', password: '', username: '' },
{
validators: {
email: v.required('Required'),
password: v.required('Required'),
username: v.required('Required')
}
}
);
// Try to submit empty
form.submit();
// Count errors
const errorCount = Object.keys(form.errors).length;
console.log(`You have ${errorCount} errors`);
// "You have 3 errors"Reading Errors
Pattern 1: Get First Error
const form = Forms.create(
{ email: '', password: '', username: '' },
{ /* validators */ }
);
form.submit(); // Trigger all validations
// Get first error message
const firstError = Object.values(form.errors)[0];
console.log('First error:', firstError);Pattern 2: Check Specific Field
const form = Forms.create(
{ email: '' },
{ /* validators */ }
);
form.setValue('email', 'invalid');
// Safe check with optional chaining
const emailError = form.errors.email;
if (emailError) {
document.getElementById('email-error').textContent = emailError;
}Pattern 3: Get All Error Messages
const form = Forms.create(
{ email: '', password: '', username: '' },
{ /* validators */ }
);
form.submit();
// Get array of error messages
const errorMessages = Object.values(form.errors);
console.log(errorMessages);
// ['Invalid email', 'Password too short', 'Username required']
// Display in alert
if (errorMessages.length > 0) {
alert('Errors:\n' + errorMessages.join('\n'));
}Pattern 4: Check Error Existence
const form = Forms.create(
{ email: '' },
{ /* validators */ }
);
// Method 1: Direct check
const hasEmailError = !!form.errors.email;
// Method 2: Use hasError() method
const hasError = form.hasError('email');
// Method 3: Check if any errors exist
const hasAnyErrors = Object.keys(form.errors).length > 0;
// Method 4: Use computed property
const hasErrors = form.hasErrors;Setting Errors
Pattern 1: Manual Server Validation
const form = Forms.create(
{ email: '' },
{
onSubmit: async (values) => {
const response = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(values)
});
if (!response.ok) {
const { field, message } = await response.json();
// Set error from server
form.setError(field, message);
throw new Error('Validation failed');
}
return response.json();
}
}
);
// Try to submit
await form.submit();
// If email already exists:
// form.errors.email = 'Email already taken'Pattern 2: Custom Async Validation
const { v } = Forms;
const form = Forms.create(
{ username: '' },
{
validators: {
username: v.combine(
v.required('Required'),
v.minLength(3, 'Too short')
)
}
}
);
// Async check after sync validation
form.setValue('username', 'alice');
// Sync validation passes
if (!form.errors.username) {
// Now do async check
const response = await fetch(`/api/check-username?name=alice`);
const { available } = await response.json();
if (!available) {
form.setError('username', 'Username already taken');
}
}
console.log(form.errors.username);
// 'Username already taken'Pattern 3: Conditional Errors
const form = Forms.create({
agreeToTerms: false,
age: ''
});
// Custom validation on submit
async function handleSubmit() {
form.clearErrors();
// Custom checks
if (!form.values.agreeToTerms) {
form.setError('agreeToTerms', 'You must accept the terms');
}
if (Number(form.values.age) < 18) {
form.setError('age', 'Must be 18 or older');
}
if (Object.keys(form.errors).length > 0) {
console.log('Validation failed:', form.errors);
return;
}
// Proceed with submission
await form.submit();
}Pattern 4: Batch Error Setting
const form = Forms.create({
email: '',
password: '',
username: ''
});
// Server returns multiple errors
const serverErrors = {
email: 'Email already exists',
username: 'Username taken',
password: 'Password too common'
};
// Set all errors at once
form.setErrors(serverErrors);
console.log(form.errors);
// {
// email: 'Email already exists',
// username: 'Username taken',
// password: 'Password too common'
// }Advanced Patterns
Pattern 1: Error Summary Component
const { v } = Forms;
const form = Forms.create(
{ email: '', password: '', username: '' },
{
validators: {
email: v.email('Invalid email'),
password: v.minLength(8, 'Min 8 chars'),
username: v.minLength(3, 'Min 3 chars')
}
}
);
// Create error summary
function renderErrorSummary() {
const errorCount = Object.keys(form.errors).length;
if (errorCount === 0) {
return '<p class="success">Form is valid! ✓</p>';
}
const errorList = Object.entries(form.errors)
.map(([field, message]) => `<li>${field}: ${message}</li>`)
.join('');
return `
<div class="error-summary">
<h3>Please fix ${errorCount} error(s):</h3>
<ul>${errorList}</ul>
</div>
`;
}
// Update on change
effect(() => {
const _ = form.errors; // Track changes
document.getElementById('summary').innerHTML = renderErrorSummary();
});Pattern 2: Error Toast Notifications
const form = Forms.create(
{ email: '' },
{ /* validators */ }
);
// Watch for new errors
let previousErrors = {};
effect(() => {
const currentErrors = { ...form.errors };
// Check for new errors
Object.entries(currentErrors).forEach(([field, message]) => {
if (!previousErrors[field]) {
// New error - show toast
showToast(`${field}: ${message}`, 'error');
}
});
previousErrors = currentErrors;
});
function showToast(message, type) {
console.log(`[${type.toUpperCase()}] ${message}`);
// Your toast notification library
}Pattern 3: Field-Level Error Display
const { v } = Forms;
const form = Forms.create(
{ email: '', password: '' },
{
validators: {
email: v.email('Invalid'),
password: v.minLength(8, 'Too short')
}
}
);
// Bind errors to specific fields
function bindErrorToField(fieldName, errorElementId) {
effect(() => {
const error = form.errors[fieldName];
const errorEl = document.getElementById(errorElementId);
if (error && form.touched[fieldName]) {
errorEl.textContent = error;
errorEl.style.display = 'block';
} else {
errorEl.textContent = '';
errorEl.style.display = 'none';
}
});
}
bindErrorToField('email', 'email-error');
bindErrorToField('password', 'password-error');Pattern 4: Error Analytics
const form = Forms.create(
{ email: '', password: '', username: '' },
{ /* validators */ }
);
// Track which fields fail most often
const errorStats = {};
effect(() => {
Object.keys(form.errors).forEach(field => {
errorStats[field] = (errorStats[field] || 0) + 1;
});
});
// On form unmount
function getErrorStats() {
console.log('Fields with most errors:', errorStats);
// { email: 5, password: 3, username: 2 }
// Email had 5 validation failures
}Pattern 5: Progressive Error Display
const { v } = Forms;
const form = Forms.create(
{ password: '' },
{
validators: {
password: (value) => {
if (!value) return 'Password required';
if (value.length < 8) return 'At least 8 characters needed';
if (!/[A-Z]/.test(value)) return 'Need uppercase letter';
if (!/[0-9]/.test(value)) return 'Need number';
return null;
}
}
}
);
// Show progressively as user types
form.setValue('password', '');
console.log(form.errors.password); // 'Password required'
form.setValue('password', 'short');
console.log(form.errors.password); // 'At least 8 characters needed'
form.setValue('password', 'lowercase');
console.log(form.errors.password); // 'Need uppercase letter'
form.setValue('password', 'Password');
console.log(form.errors.password); // 'Need number'
form.setValue('password', 'Password1');
console.log(form.errors.password); // undefined ✅Common Pitfalls
Pitfall 1: Checking Errors Before Setting Value
❌ Wrong:
const { v } = Forms;
const form = Forms.create(
{ email: '' },
{ validators: { email: v.email('Invalid') } }
);
// Check error before setting value
if (form.errors.email) {
console.log('Has error'); // Won't run - no error yet!
}
form.setValue('email', 'invalid');
// NOW it has an error✅ Correct:
const { v } = Forms;
const form = Forms.create(
{ email: '' },
{ validators: { email: v.email('Invalid') } }
);
form.setValue('email', 'invalid');
// Now check
if (form.errors.email) {
console.log('Has error:', form.errors.email); // Works!
}Pitfall 2: Direct Mutation of Errors Object
❌ Wrong:
const form = Forms.create({ email: '' });
// Don't mutate directly!
form.errors.email = 'Custom error';
// May not trigger reactivity✅ Correct:
const form = Forms.create({ email: '' });
// Use the method
form.setError('email', 'Custom error');
// Triggers reactivity ✅Pitfall 3: Assuming Empty Errors Means Valid
❌ Wrong:
const form = Forms.create(
{ email: '' },
{ validators: { email: v.email('Invalid') } }
);
// Check if valid
if (Object.keys(form.errors).length === 0) {
console.log('Valid!');
// Might be true even if not validated yet!
}✅ Correct:
const form = Forms.create(
{ email: '' },
{ validators: { email: v.email('Invalid') } }
);
// Use the computed property
if (form.isValid) {
console.log('Valid!');
// Properly checks validation state ✅
}
// Or validate first
const isValid = form.validate();
if (isValid && Object.keys(form.errors).length === 0) {
console.log('Valid!');
}Pitfall 4: Not Clearing Errors After Fix
❌ Wrong:
const form = Forms.create({ email: '' });
// Manually set error
form.setError('email', 'Email taken');
// User fixes it
form.setValue('email', 'newemail@example.com');
// Error still there! (manual errors don't auto-clear)
console.log(form.errors.email); // 'Email taken'✅ Correct:
const form = Forms.create({ email: '' });
form.setError('email', 'Email taken');
// Clear when user changes value
form.setValue('email', 'newemail@example.com');
form.clearError('email'); // Clear manual error
console.log(form.errors.email); // undefined ✅Pitfall 5: Displaying Errors for Untouched Fields
❌ Wrong:
const form = Forms.create(
{ email: '', password: '' },
{ /* validators */ }
);
// Show all errors immediately
Object.entries(form.errors).forEach(([field, error]) => {
document.getElementById(`${field}-error`).textContent = error;
});
// Shows errors for fields user hasn't touched yet!✅ Correct:
const form = Forms.create(
{ email: '', password: '' },
{ /* validators */ }
);
// Show errors only for touched fields
Object.entries(form.errors).forEach(([field, error]) => {
if (form.touched[field]) {
document.getElementById(`${field}-error`).textContent = error;
}
});
// Only shows errors for fields user interacted with ✅Summary
Key Takeaways
form.errorsstores validation error messages for each field that fails validation.It's automatically updated when validators run during
setValue()orvalidate().Errors are removed automatically when validation passes for a field.
Read it to display error messages to users in your UI.
Use
setError()for manual/server errors - validator errors update automatically.Use
clearError()to remove manual errors - validator errors clear when validation passes.Check
form.isValidfor overall validity instead of manually checking if errors is empty.Combine with
form.touchedto show errors only for fields the user has interacted with.form.errorFieldscomputed property gives you an array of fields that have errors.It's reactive - changes trigger effects and UI updates automatically.
One-Line Rule
form.errorstracks validation failures - read it to display messages, use methods to set/clear manual errors, and rely on automatic updates for validator errors.
What's Next?
- Learn about
form.touchedto track user interaction - Explore
form.isValidto check overall form validity - Master
form.hasErrorscomputed property - Discover error display patterns and best practices