React Form Validation and Error Handling – Complete Beginner’s Guide (2025)
Form validation is essential for creating reliable and user-friendly web applications. It ensures that users provide correct and complete data before submitting a form.
In React, form validation can be done using built-in JavaScript logic, custom rules, or third-party libraries such as Formik and Yup.
In this tutorial, you’ll learn both manual validation and library-based validation step by step.
Basic Form Validation (Manual Approach)
Let’s begin with a simple example using React’s useState hook and conditional checks.
Example: Validate Email and Password
import React, { useState } from "react";
function SimpleForm() {
const [formData, setFormData] = useState({ email: "", password: "" });
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const validate = () => {
let tempErrors = {};
if (!formData.email) {
tempErrors.email = "Email is required";
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
tempErrors.email = "Email is invalid";
}
if (!formData.password) {
tempErrors.password = "Password is required";
} else if (formData.password.length < 6) {
tempErrors.password = "Password must be at least 6 characters";
}
setErrors(tempErrors);
return Object.keys(tempErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
alert("Form submitted successfully!");
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Email:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <p style={{ color: "red" }}>{errors.email}</p>}
</div>
<div>
<label>Password:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <p style={{ color: "red" }}>{errors.password}</p>}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default SimpleForm;
Explanation:
validate()checks input fields before form submission.If errors exist, they’re stored in
errorsstate and displayed to the user.Only valid data triggers the success message.
Real-Time Validation
You can show validation messages as the user types instead of waiting until form submission.
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
// Real-time validation
if (name === "email" && !/\S+@\S+\.\S+/.test(value)) {
setErrors({ ...errors, email: "Invalid email format" });
} else {
setErrors({ ...errors, email: "" });
}
};
This provides a smoother, real-time feedback experience.
Using Formik and Yup (Library Approach)
For complex forms, using libraries like Formik (for handling forms) and Yup (for validation) makes code cleaner and scalable.
Installation
npm install formik yupExample: Formik + Yup Validation
import React from "react";
import { useFormik } from "formik";
import * as Yup from "yup";
function FormikForm() {
const formik = useFormik({
initialValues: {
name: "",
email: "",
},
validationSchema: Yup.object({
name: Yup.string()
.min(2, "Name must be at least 2 characters")
.required("Name is required"),
email: Yup.string()
.email("Invalid email address")
.required("Email is required"),
}),
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label>Name:</label>
<input
name="name"
value={formik.values.name}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.name && formik.errors.name && (
<p style={{ color: "red" }}>{formik.errors.name}</p>
)}
<label>Email:</label>
<input
name="email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.email && formik.errors.email && (
<p style={{ color: "red" }}>{formik.errors.email}</p>
)}
<button type="submit">Submit</button>
</form>
);
}
export default FormikForm;
Why Formik + Yup?
Handles form state, submission, and validation automatically.
Reduces repetitive code.
Scales easily for large, multi-page forms.
Error Handling During Form Submission
Sometimes, validation passes but a server error occurs (like a failed API request).
You can handle such cases with try...catch and display user-friendly messages.
const handleSubmit = async (e) => {
e.preventDefault();
if (validate()) {
try {
const response = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify(formData),
});
if (!response.ok) throw new Error("Server error");
alert("Form submitted successfully!");
} catch (error) {
setErrors({ api: "Failed to submit form. Please try again." });
}
}
};
Displaying Error Messages Clearly
Good UI practices for form errors:
- Place error messages directly below the input field.
- Use red color and clear, short text.
- Disable submit button until all validations pass.
- Use placeholders or hints for expected input formats.