How to create a form with validation in React

Creating forms with validation in React ensures data quality and provides immediate feedback to users about input errors and requirements. As the creator of CoreUI with extensive React development experience since 2014, I’ve built countless validated forms in enterprise applications for user registration and data collection. The most reliable approach uses controlled components with state-based validation that checks inputs in real-time. This pattern provides immediate user feedback while maintaining full control over form behavior and submission logic.

Create a controlled form component with state-based validation that provides real-time feedback on input errors.

import { useState } from 'react'

function UserForm() {
    const [formData, setFormData] = useState({
        email: '',
        password: '',
        confirmPassword: ''
    })

    const [errors, setErrors] = useState({})
    const [touched, setTouched] = useState({})

    const validateField = (name, value) => {
        switch (name) {
            case 'email':
                return /\S+@\S+\.\S+/.test(value) ? '' : 'Invalid email address'
            case 'password':
                return value.length >= 8 ? '' : 'Password must be at least 8 characters'
            case 'confirmPassword':
                return value === formData.password ? '' : 'Passwords do not match'
            default:
                return ''
        }
    }

    const handleChange = (e) => {
        const { name, value } = e.target
        setFormData(prev => ({ ...prev, [name]: value }))

        if (touched[name]) {
            setErrors(prev => ({
                ...prev,
                [name]: validateField(name, value)
            }))
        }
    }

    const handleBlur = (e) => {
        const { name, value } = e.target
        setTouched(prev => ({ ...prev, [name]: true }))
        setErrors(prev => ({ ...prev, [name]: validateField(name, value) }))
    }

    const handleSubmit = (e) => {
        e.preventDefault()
        const newErrors = {}
        Object.keys(formData).forEach(key => {
            newErrors[key] = validateField(key, formData[key])
        })
        setErrors(newErrors)

        if (Object.values(newErrors).every(error => error === '')) {
            console.log('Form submitted:', formData)
        }
    }

    return (
        <form onSubmit={handleSubmit}>
            <input
                name="email"
                type="email"
                placeholder="Email"
                value={formData.email}
                onChange={handleChange}
                onBlur={handleBlur}
            />
            {touched.email && errors.email && <span>{errors.email}</span>}

            <input
                name="password"
                type="password"
                placeholder="Password"
                value={formData.password}
                onChange={handleChange}
                onBlur={handleBlur}
            />
            {touched.password && errors.password && <span>{errors.password}</span>}

            <input
                name="confirmPassword"
                type="password"
                placeholder="Confirm Password"
                value={formData.confirmPassword}
                onChange={handleChange}
                onBlur={handleBlur}
            />
            {touched.confirmPassword && errors.confirmPassword && <span>{errors.confirmPassword}</span>}

            <button type="submit">Submit</button>
        </form>
    )
}

This code creates a fully controlled form with real-time validation that checks each field when the user leaves the input (onBlur) and updates validation as they type if the field has been touched. The validation logic is centralized in the validateField function, making it easy to maintain and extend. Errors are only shown after the user has interacted with a field, providing a smooth user experience.

Best Practice Note:

This is the form validation pattern we use in CoreUI dashboard components for consistent user input handling. Consider using a validation library like Yup or Zod for complex validation rules and better type safety in larger applications.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author