This content originally appeared on DEV Community and was authored by Vijay Kumar
Forms are at the heart of most web applications. Whether you’re building a login page, a sign-up flow, or a multi-step wizard, you’ll deal with user input β and that means forms.
While React makes UI composition elegant, form handling can quickly become messy if not structured properly. In this guide, weβll break down how to handle forms in React like a pro, with clean code, reusable patterns, and modern best practices.
The Basics: Controlled vs Uncontrolled
React offers two ways to work with form inputs:
- Controlled Components: React state is the single source of truth.
- Uncontrolled Components: DOM handles the form state via refs.
Pro Tip: Always default to controlled components unless you have a performance bottleneck. They make debugging and validation easier.
Example: Controlled Input
const [name, setName] = useState("");
return (
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
Handling Multiple Inputs Elegantly
When you have multiple fields, managing individual state variables gets messy.
Use a Single State Object
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
<input name="password" value={formData.password} onChange={handleChange} />
Pro Tip: Name your inputs to match keys in
formData
.
Form Validation: Donβt Reinvent the Wheel
Option 1: Manual Validation
const validate = () => {
if (!formData.email.includes("@")) {
setError("Email is invalid");
return false;
}
return true;
};
Option 2: Use Form Libraries
For large forms or complex validation, use Formik, React Hook Form, or Zod/Yup for schema-based validation.
React Hook Form + Zod Example
npm install react-hook-form zod @hookform/resolvers
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
<input {...register("password")} />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Submit</button>
</form>
);
Pro Tip: Validation libraries reduce boilerplate, improve readability, and help with edge cases.
Use Custom Hooks to Encapsulate Logic
Extract form logic into custom hooks to reuse across components.
function useLoginForm() {
const [formData, setFormData] = useState({ email: "", password: "" });
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
return { formData, handleChange };
}
UX Enhancements
- Disable the submit button until the form is valid.
- Show real-time validation errors.
- Use loading indicators on submit.
- Auto-focus the first invalid field.
<button disabled={!formIsValid || isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
Summary
To handle forms in React like a pro:
- Prefer controlled components.
- Use a single state object for multiple fields.
- Leverage form libraries for complex forms.
- Encapsulate logic in custom hooks.
- Enhance user experience with thoughtful UX patterns.
Mastering forms isnβt about writing more code β itβs about writing less, smarter code.
Further Reading
Need a code template or want help converting a form to React Hook Form? Just drop your code in the comments β happy to help!
This content originally appeared on DEV Community and was authored by Vijay Kumar