Part 11: Building Robust Forms & Validation Patterns
Managing multiple input fields with individual useState hooks quickly becomes unmaintainable. In this part, we learn how to manage complex forms with a single unified state object and implement custom validation.
1. Managing Multi-Field State with Computed Property Names
Instead of declaring ten useState variables for ten fields, store form data in a single object and use dynamic ES6 property names [e.target.name]:
import { useState } from 'react';
export default function RegistrationForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
agreeToTerms: false
});
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const validate = () => {
const newErrors = {};
if (!formData.username.trim()) newErrors.username = 'Username is required.';
if (!formData.email.includes('@')) newErrors.email = 'Valid email is required.';
if (formData.password.length < 8) newErrors.password = 'Password must be at least 8 characters.';
if (!formData.agreeToTerms) newErrors.agreeToTerms = 'You must accept the terms.';
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const validationErrors = validate();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
setErrors({});
console.log('Submitted successfully:', formData);
};
return (
<form onSubmit={handleSubmit} className="max-w-md p-6 border rounded shadow">
<div className="mb-4">
<label>Username</label>
<input name="username" value={formData.username} onChange={handleChange} className="input" />
{errors.username && <p className="text-red-500 text-sm">{errors.username}</p>}
</div>
<div className="mb-4">
<label>Email</label>
<input name="email" type="email" value={formData.email} onChange={handleChange} className="input" />
{errors.email && <p className="text-red-500 text-sm">{errors.email}</p>}
</div>
<div className="mb-4">
<label>Password</label>
<input name="password" type="password" value={formData.password} onChange={handleChange} className="input" />
{errors.password && <p className="text-red-500 text-sm">{errors.password}</p>}
</div>
<div className="mb-4 flex items-center">
<input name="agreeToTerms" type="checkbox" checked={formData.agreeToTerms} onChange={handleChange} className="mr-2" />
<label>I agree to terms & conditions</label>
</div>
{errors.agreeToTerms && <p className="text-red-500 text-sm">{errors.agreeToTerms}</p>}
<button type="submit" className="btn-primary w-full mt-4">Create Account</button>
</form>
);
}
Key Takeaways
- Manage multi-field state using an object and computed property keys
[name]: value. - Separate form values from validation error state.
- Validate on submit or inline on blur/change for enhanced user feedback.
In Part 12, we will explore modern Styling Strategies in React: CSS Modules, Tailwind CSS, and CSS-in-JS!

