React Forms
HTML forms maintain their own state in the DOM. In React, we prefer controlled components โ the React state is the single source of truth for form values.
Controlled Inputs
function BasicForm() {
const [form, setForm] = useState({
username: "",
email: "",
password: "",
role: "user",
subscribe: false,
bio: "",
});
// Single handler for all field types
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setForm(prev => ({
...prev,
[name]: type === "checkbox" ? checked : value,
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("Submitted:", form);
};
return (
<form onSubmit={handleSubmit}>
{/* Text input */}
<input name="username" value={form.username} onChange={handleChange} />
{/* Email */}
<input name="email" type="email" value={form.email} onChange={handleChange} />
{/* Password */}
<input name="password" type="password" value={form.password} onChange={handleChange} />
{/* Select */}
<select name="role" value={form.role} onChange={handleChange}>
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="moderator">Moderator</option>
</select>
{/* Checkbox */}
<label>
<input name="subscribe" type="checkbox" checked={form.subscribe} onChange={handleChange} />
Subscribe to newsletter
</label>
{/* Textarea */}
<textarea name="bio" value={form.bio} onChange={handleChange} />
<button type="submit">Register</button>
</form>
);
}
Form Validation
function LoginForm() {
const [values, setValues] = useState({ email: "", password: "" });
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const validate = (fields) => {
const errs = {};
if (!fields.email.includes("@")) errs.email = "Enter a valid email";
if (fields.password.length < 8) errs.password = "Min 8 characters";
return errs;
};
const handleChange = (e) => {
const updated = { ...values, [e.target.name]: e.target.value };
setValues(updated);
if (touched[e.target.name]) {
setErrors(validate(updated)); // Live validate after first blur
}
};
const handleBlur = (e) => {
setTouched(prev => ({ ...prev, [e.target.name]: true }));
setErrors(validate(values));
};
const handleSubmit = (e) => {
e.preventDefault();
setTouched({ email: true, password: true }); // Show all errors
const errs = validate(values);
setErrors(errs);
if (Object.keys(errs).length === 0) {
console.log("Login:", values);
}
};
return (
<form onSubmit={handleSubmit}>
<input
name="email" type="email" value={values.email}
onChange={handleChange} onBlur={handleBlur}
/>
{errors.email && <p className="error">{errors.email}</p>}
<input
name="password" type="password" value={values.password}
onChange={handleChange} onBlur={handleBlur}
/>
{errors.password && <p className="error">{errors.password}</p>}
<button type="submit">Log in</button>
</form>
);
}
TipFor complex forms in real projects, consider React Hook Form (
npm install react-hook-form). It reduces re-renders, simplifies validation, and integrates with Zod or Yup for schema validation.