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.



