form.setError()
Quick Start (30 seconds)
const form = Forms.create({
email: '',
password: ''
});
// Set a custom error manually
form.setError('email', 'This email is already taken');
console.log(form.errors.email); // 'This email is already taken'
console.log(form.hasErrors); // true
console.log(form.errorFields); // ['email']
// Clear the error
form.setError('email', '');
console.log(form.errors.email); // ''
console.log(form.hasErrors); // false
// Chain multiple error assignments
form
.setError('email', 'Invalid email')
.setError('password', 'Too weak');What just happened? setError() manually sets error messages - perfect for server-side validation or custom checks!
What is form.setError()?
form.setError() is the manual error assignment method for setting custom validation errors on individual fields.
Simply put, it's how you tell the form "this field has an error" when automatic validation isn't enough.
Key characteristics:
- ✅ Sets custom error message for a field
- ✅ Bypasses automatic validators
- ✅ Perfect for server-side validation errors
- ✅ Updates all reactive UI automatically
- ✅ Returns the form instance for chaining
Syntax
// Set an error
form.setError(field, errorMessage)
// Clear an error
form.setError(field, '')
// Chain multiple errors
form
.setError('field1', 'Error 1')
.setError('field2', 'Error 2');Parameters:
field(string) - The name of the fielderrorMessage(string) - The error message (empty string clears the error)
Returns: The form instance (this) for method chaining
Why Does This Exist?
The Challenge with Automatic Validation
Built-in validators run client-side and can't handle server-side checks like:
- Username/email uniqueness
- Database constraints
- External API validation
- Business rules requiring server data
const form = Forms.create(
{ email: '' },
{
email: (value) => !value.includes('@') ? 'Invalid format' : ''
// ❌ Can't check if email exists in database
}
);
// ✅ setError() handles server-side validation
async function checkEmailAvailability(email) {
const response = await fetch(`/api/check-email?email=${email}`);
const { available } = await response.json();
if (!available) {
form.setError('email', 'This email is already registered');
}
}When to use setError(): ✅ Server-side validation responses ✅ Async validation results ✅ Custom business logic errors ✅ Conditional validation based on external data ✅ Manual error injection for testing
Mental Model
Think of setError() as manual override for the error system.
Automatic Validation Flow
User types → setValue() → Validator runs → Error set automatically
↓
Client-side rules onlyManual Error Setting Flow
User submits → Server validates → Response contains error
↓
setError() → Error displayed
↓
Server-side rules includedHow Does It Work?
Internal Process
// When you call:
form.setError('email', 'This email is taken');
// Here's what happens internally:
1️⃣ Update the errors object
form.errors.email = 'This email is taken'
2️⃣ Trigger reactive updates
- form.hasErrors recalculates → true
- form.isValid recalculates → false
- form.errorFields updates → includes 'email'
3️⃣ All reactive effects fire
- Error message UI updates
- Submit button might disable
- Field styling changes to error state
4️⃣ Return form instance for chaining
return thisReactivity Flow Diagram
setError('email', 'Email taken')
↓
Update form.errors.email
↓
Reactive properties update:
- hasErrors → true
- isValid → false
- errorFields → ['email', ...]
↓
All effects fire automatically
↓
UI updates (error displays, styling, etc.)Basic Usage
Example 1: Server-Side Validation
const form = Forms.create({
username: '',
email: ''
});
async function handleSubmit() {
const response = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(form.values)
});
if (!response.ok) {
const { errors } = await response.json();
// Set server errors
if (errors.username) {
form.setError('username', errors.username);
}
if (errors.email) {
form.setError('email', errors.email);
}
return;
}
console.log('Registration successful!');
}
// Server responds with: { errors: { email: 'Email already exists' } }
// Form now shows: form.errors.email = 'Email already exists'Example 2: Async Field Validation
const form = Forms.create({
username: ''
});
let checkTimeout;
async function checkUsernameAvailability(username) {
// Clear previous timeout
clearTimeout(checkTimeout);
// Debounce the check
checkTimeout = setTimeout(async () => {
const response = await fetch(`/api/check-username?username=${username}`);
const { available } = await response.json();
if (!available) {
form.setError('username', 'Username is already taken');
} else {
form.setError('username', ''); // Clear error
}
}, 500);
}
// Connect to input
usernameInput.addEventListener('input', (e) => {
form.setValue('username', e.target.value);
checkUsernameAvailability(e.target.value);
});Example 3: Custom Business Rules
const form = Forms.create({
age: 0,
parentalConsent: false
});
function validateAge() {
const age = form.getValue('age');
const hasConsent = form.getValue('parentalConsent');
if (age < 18 && !hasConsent) {
form.setError('parentalConsent', 'Parental consent required for users under 18');
} else {
form.setError('parentalConsent', '');
}
}
// Validate on changes
effect(() => {
form.getValue('age');
form.getValue('parentalConsent');
validateAge();
});Example 4: Clear Errors on Field Change
const form = Forms.create({
email: ''
});
// Set server error
form.setError('email', 'Email already exists');
// Clear error when user modifies the field
emailInput.addEventListener('input', (e) => {
form.setValue('email', e.target.value);
// Clear server error when user types
form.setError('email', '');
});Example 5: Display Error Messages
const form = Forms.create({
email: '',
password: ''
});
// Reactive error display
effect(() => {
const emailError = form.errors.email;
const emailErrorEl = document.getElementById('email-error');
if (emailError) {
emailErrorEl.textContent = emailError;
emailErrorEl.style.display = 'block';
} else {
emailErrorEl.style.display = 'none';
}
});
// Later: server returns error
form.setError('email', 'Email format not accepted by our system');
// Error displays automaticallyAdvanced Patterns
Pattern 1: Multi-Field Cross-Validation
const form = Forms.create({
startDate: '',
endDate: ''
});
function validateDateRange() {
const start = new Date(form.getValue('startDate'));
const end = new Date(form.getValue('endDate'));
if (start > end) {
form.setError('endDate', 'End date must be after start date');
} else {
form.setError('endDate', '');
}
}
// Validate whenever dates change
effect(() => {
form.getValue('startDate');
form.getValue('endDate');
validateDateRange();
});Pattern 2: Rate Limiting Error
const form = Forms.create({
email: ''
});
let requestCount = 0;
const MAX_REQUESTS = 5;
async function handleSubmit() {
requestCount++;
if (requestCount > MAX_REQUESTS) {
form.setError('email', 'Too many requests. Please try again later.');
return;
}
// Proceed with submission
const response = await fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify({ email: form.getValue('email') })
});
if (response.ok) {
requestCount = 0; // Reset on success
}
}Pattern 3: Conditional Error Based on Other Fields
const form = Forms.create({
paymentMethod: 'credit_card',
creditCardNumber: '',
paypalEmail: ''
});
effect(() => {
const method = form.getValue('paymentMethod');
if (method === 'credit_card') {
const cardNumber = form.getValue('creditCardNumber');
if (!cardNumber) {
form.setError('creditCardNumber', 'Credit card number required');
} else {
form.setError('creditCardNumber', '');
}
// Clear PayPal errors
form.setError('paypalEmail', '');
} else if (method === 'paypal') {
const email = form.getValue('paypalEmail');
if (!email) {
form.setError('paypalEmail', 'PayPal email required');
} else {
form.setError('paypalEmail', '');
}
// Clear credit card errors
form.setError('creditCardNumber', '');
}
});Pattern 4: Password Strength Validator
const form = Forms.create({
password: ''
});
function validatePasswordStrength(password) {
const errors = [];
if (password.length < 8) {
errors.push('at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('one lowercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('one number');
}
if (!/[^A-Za-z0-9]/.test(password)) {
errors.push('one special character');
}
if (errors.length > 0) {
form.setError('password', `Password must contain ${errors.join(', ')}`);
} else {
form.setError('password', '');
}
}
passwordInput.addEventListener('input', (e) => {
const password = e.target.value;
form.setValue('password', password);
validatePasswordStrength(password);
});Pattern 5: API Error Mapping
const form = Forms.create({
username: '',
email: '',
password: ''
});
async function handleSubmit() {
try {
const response = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(form.values)
});
if (!response.ok) {
const data = await response.json();
// Map API error codes to user-friendly messages
const errorMap = {
'EMAIL_EXISTS': 'This email is already registered',
'USERNAME_TAKEN': 'This username is not available',
'WEAK_PASSWORD': 'Password is too weak',
'INVALID_EMAIL': 'Email format is invalid'
};
if (data.errors) {
Object.entries(data.errors).forEach(([field, errorCode]) => {
const message = errorMap[errorCode] || 'Invalid value';
form.setError(field, message);
});
}
return;
}
console.log('Success!');
} catch (error) {
form.setError('email', 'Network error. Please try again.');
}
}Pattern 6: Real-Time Validation Feedback
const form = Forms.create({
promoCode: ''
});
let validateTimeout;
async function validatePromoCode(code) {
clearTimeout(validateTimeout);
if (!code) {
form.setError('promoCode', '');
return;
}
validateTimeout = setTimeout(async () => {
// Show loading state
form.setError('promoCode', 'Validating...');
const response = await fetch(`/api/validate-promo?code=${code}`);
const { valid, message } = await response.json();
if (valid) {
form.setError('promoCode', ''); // Clear error
showSuccessMessage('Promo code applied!');
} else {
form.setError('promoCode', message || 'Invalid promo code');
}
}, 500);
}
promoInput.addEventListener('input', (e) => {
form.setValue('promoCode', e.target.value);
validatePromoCode(e.target.value);
});Pattern 7: File Upload Validation
const form = Forms.create({
avatar: null
});
function validateFile(file) {
if (!file) {
form.setError('avatar', 'Please select a file');
return false;
}
// Check file size (max 5MB)
const maxSize = 5 * 1024 * 1024;
if (file.size > maxSize) {
form.setError('avatar', 'File size must be less than 5MB');
return false;
}
// Check file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(file.type)) {
form.setError('avatar', 'Only JPEG, PNG, and GIF files are allowed');
return false;
}
// All checks passed
form.setError('avatar', '');
return true;
}
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
form.setValue('avatar', file);
validateFile(file);
});Pattern 8: Dependency Chain Validation
const form = Forms.create({
country: '',
state: '',
city: ''
});
effect(() => {
const country = form.getValue('country');
const state = form.getValue('state');
const city = form.getValue('city');
// Clear all location errors first
form.setError('country', '');
form.setError('state', '');
form.setError('city', '');
if (!country) {
form.setError('country', 'Country is required');
return; // Stop cascade
}
if (!state) {
form.setError('state', 'State is required');
return; // Stop cascade
}
if (!city) {
form.setError('city', 'City is required');
}
});Pattern 9: Debounced Uniqueness Check
const form = Forms.create({
companyName: ''
});
let uniquenessCheck;
async function checkCompanyNameUnique(name) {
clearTimeout(uniquenessCheck);
if (!name || name.length < 3) {
form.setError('companyName', '');
return;
}
uniquenessCheck = setTimeout(async () => {
const response = await fetch(`/api/check-company?name=${encodeURIComponent(name)}`);
const { unique, suggestions } = await response.json();
if (!unique) {
let errorMsg = 'This company name is already registered';
if (suggestions && suggestions.length > 0) {
errorMsg += `. Suggestions: ${suggestions.join(', ')}`;
}
form.setError('companyName', errorMsg);
} else {
form.setError('companyName', '');
}
}, 800);
}
companyInput.addEventListener('input', (e) => {
form.setValue('companyName', e.target.value);
checkCompanyNameUnique(e.target.value);
});Pattern 10: Error Priority System
const form = Forms.create({
email: ''
});
const errorPriority = {
required: 1,
format: 2,
availability: 3,
server: 4
};
const currentErrors = {
email: []
};
function addError(field, type, message) {
currentErrors[field] = currentErrors[field] || [];
currentErrors[field].push({ type, message, priority: errorPriority[type] });
updateDisplayedError(field);
}
function updateDisplayedError(field) {
const errors = currentErrors[field];
if (!errors || errors.length === 0) {
form.setError(field, '');
return;
}
// Show highest priority error
const highestPriority = errors.reduce((prev, curr) =>
prev.priority < curr.priority ? prev : curr
);
form.setError(field, highestPriority.message);
}
function clearErrorType(field, type) {
if (currentErrors[field]) {
currentErrors[field] = currentErrors[field].filter(e => e.type !== type);
updateDisplayedError(field);
}
}
// Usage:
addError('email', 'required', 'Email is required');
addError('email', 'format', 'Invalid email format');
// Shows: 'Email is required' (higher priority)
clearErrorType('email', 'required');
// Now shows: 'Invalid email format'Common Pitfalls
Pitfall 1: Forgetting to Clear Errors
const form = Forms.create({
email: ''
});
// Set server error
form.setError('email', 'Email already exists');
// ❌ User changes email but error persists
emailInput.addEventListener('input', (e) => {
form.setValue('email', e.target.value);
// Error still shows "Email already exists"
});
// ✅ Clear error when user types
emailInput.addEventListener('input', (e) => {
form.setValue('email', e.target.value);
form.setError('email', ''); // Clear old server error
});Pitfall 2: Overwriting Validator Errors
const form = Forms.create(
{ email: '' },
{
email: (value) => !value.includes('@') ? 'Invalid format' : ''
}
);
// ❌ setError() can overwrite validator errors
form.setValue('email', 'bad-email'); // Validator sets error
form.setError('email', ''); // Oops, cleared validator error
// ✅ Be aware of validator/manual error interaction
// Consider using different fields or clear patternsPitfall 3: Not Handling Empty Strings vs Null
const form = Forms.create({
field: ''
});
// ❌ Both clear the error, but be consistent
form.setError('field', '');
form.setError('field', null); // Works but inconsistent
// ✅ Use empty string consistently
form.setError('field', '');Pitfall 4: Setting Errors on Non-Existent Fields
const form = Forms.create({
email: '',
password: ''
});
// ❌ Field doesn't exist in form
form.setError('username', 'Username taken');
// Error is set but field doesn't exist
// ✅ Ensure field exists
if ('username' in form.values) {
form.setError('username', 'Username taken');
}Pitfall 5: Batch Errors One-by-One
const form = Forms.create({
field1: '', field2: '', field3: ''
});
// ❌ Less efficient
form.setError('field1', 'Error 1');
form.setError('field2', 'Error 2');
form.setError('field3', 'Error 3');
// ✅ Better for multiple errors - use setErrors()
form.setErrors({
field1: 'Error 1',
field2: 'Error 2',
field3: 'Error 3'
});Summary
Key Takeaways
setError()manually sets error messages for individual fields.Perfect for server-side validation - handles errors that can't be checked client-side.
Bypasses automatic validators - gives you full control over error messages.
Updates all reactive properties -
hasErrors,isValid,errorFieldsall update automatically.Returns form instance - enables method chaining.
Use empty string to clear -
form.setError('field', '')removes the error.
When to Use setError()
✅ Use setError() for:
- Server-side validation responses
- Async validation (uniqueness checks, API calls)
- Custom business logic errors
- Cross-field validation
- File upload validation
- Real-time external validation
❌ Don't use setError() for:
- Simple format validation (use validators instead)
- Batch error updates (use
setErrors()) - Basic required field checks (use validators)
One-Line Rule
form.setError(field, message)manually sets a custom error message - essential for server-side validation and complex async checks that automatic validators can't handle.
What's Next?
- Learn about
form.setErrors()for batch error updates - Explore
form.clearError()for removing specific errors - Master
form.hasError()for conditional error checking