A
AiTechWorlds
AiTechWorlds
React forms use controlled components React state is the single source of truth for every input value. This makes validation, conditional logic, and form submission clean and predictable.
function EmailInput() {
const [email, setEmail] = useState("");
return (
<input
type="email"
value={email} // controlled by state
onChange={e => setEmail(e.target.value)} // update state on change
placeholder="Enter email"
/>
);
}function SignupForm() {
const [form, setForm] = useState({
name: "",
email: "",
password: "",
role: "user",
terms: false
});
// Generic handler for all fields
function handleChange(e) {
const { name, value, type, checked } = e.target;
setForm(prev => ({
...prev,
[name]: type === "checkbox" ? checked : value
}));
}
return (
<form>
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" type="email" value={form.email} onChange={handleChange} />
<input name="password" type="password" value={form.password} onChange={handleChange} />
<select name="role" value={form.role} onChange={handleChange}>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<input name="terms" type="checkbox" checked={form.terms} onChange={handleChange} />
</form>
);
}function RegisterForm() {
const [form, setForm] = useState({ username: "", email: "", password: "" });
const [errors, setErrors] = useState({});
const [submitted, setSubmitted] = useState(false);
function validate(data) {
const newErrors = {};
if (!data.username.trim()) newErrors.username = "Username is required";
else if (data.username.length < 3) newErrors.username = "Must be 3+ characters";
if (!data.email.includes("@")) newErrors.email = "Valid email required";
if (data.password.length < 8) newErrors.password = "Must be 8+ characters";
return newErrors;
}
function handleChange(e) {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
// Clear error on change
if (errors[name]) setErrors(prev => ({ ...prev, [name]: "" }));
}
async function handleSubmit(e) {
e.preventDefault();
const newErrors = validate(form);
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
try {
await registerUser(form);
setSubmitted(true);
} catch (err) {
setErrors({ submit: err.message });
}
}
if (submitted) return <p>Registration successful!</p>;
return (
<form onSubmit={handleSubmit} noValidate>
<div className="field">
<label>Username</label>
<input name="username" value={form.username} onChange={handleChange}
className={errors.username ? "error" : ""} />
{errors.username && <span className="error-msg">{errors.username}</span>}
</div>
<div className="field">
<label>Email</label>
<input name="email" type="email" value={form.email} onChange={handleChange}
className={errors.email ? "error" : ""} />
{errors.email && <span className="error-msg">{errors.email}</span>}
</div>
<div className="field">
<label>Password</label>
<input name="password" type="password" value={form.password} onChange={handleChange}
className={errors.password ? "error" : ""} />
{errors.password && <span className="error-msg">{errors.password}</span>}
</div>
{errors.submit && <div className="error-banner">{errors.submit}</div>}
<button type="submit">Create Account</button>
</form>
);
}Sometimes you don't need React to track every keystroke use useRef for simpler cases:
import { useRef } from "react";
function SearchBox({ onSearch }) {
const inputRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
onSearch(inputRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} type="search" placeholder="Search..." />
<button>Search</button>
</form>
);
}Use react hook form or formik for complex production forms they handle validation, touched state, and submission with less boilerplate.
Next lesson: React Router v6 client side navigation in single page apps.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises