Part 11: Building Robust Forms & Validation Patterns

Part 11 Technology Updated September 12, 2026

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!