React
Chapter 06: Forms & User Input — Controlled vs uncontrolled inputs, unified multi-field handlers, real-time validation, checkboxes, selects, and submit mechanics.
1. Controlled vs Uncontrolled Components
In standard HTML, form elements like <input>, <textarea>, and <select> maintain their own internal DOM state. In React, we prefer Controlled Components where React component state serves as the single source of truth.
💡 Real-World Analogy: Electronic Dashboard vs Paper Ballot
Think of entering input into a form:
- Uncontrolled (Paper Ballot): The user writes on paper. The software has no idea what is written until someone walks over and reads the box on submit (using
useRef()). - Controlled (Electronic Digital Keypad): Every keystroke immediately pulses into the central computer's memory (React state). The computer validates the keystroke, formats it, and reflects it on the digital screen in real time!
Controlled Component (Standard)
State drives the input value
- Input has
value={state}andonChange={handler}. - Enables live character count, instant validation, masking, and disabling submit buttons.
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
Uncontrolled Component
DOM holds the value internally
- Uses
ref={inputRef}to pull value on submit. - Simpler for quick one-off file uploads (
<input type="file" />).
const inputRef = useRef();
// Read on submit: inputRef.current.value
2. The Unified Multi-Field Form Handler
Instead of declaring 10 separate useState hooks for 10 form fields, professional developers combine all fields into a single form state object and use computed property names ([e.target.name]):
function RegistrationForm() {
// 1. Single unified state object for all fields
const [formData, setFormData] = React.useState({
username: "anshuman_dev",
email: "anshuman@example.com",
role: "frontend",
subscribeNewsletter: true
});
// 2. Universal change handler that handles text, select, and checkboxes
function handleChange(e) {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === "checkbox" ? checked : value
}));
}
return (
<form>
<input name="username" value={formData.username} onChange={handleChange} />
<input name="email" type="email" value={formData.email} onChange={handleChange} />
<select name="role" value={formData.role} onChange={handleChange}>
<option value="frontend">Frontend Engineer</option>
<option value="backend">Backend Engineer</option>
</select>
</form>
);
}
3. Form Submission & Real-Time Validation
Always prevent default browser page reload via e.preventDefault() and validate state before transmitting data to your backend API:
⚠️ Always Prevent Default on Form Submission
function SignUpForm() {
const [email, setEmail] = React.useState("");
const [error, setError] = React.useState("");
function handleSubmit(e) {
e.preventDefault(); // 🛑 Critical: Stops browser from reloading the whole page!
if (!email.includes("@")) {
setError("Please enter a valid email address.");
return;
}
setError("");
console.log("Submitting payload to server:", { email });
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => { setEmail(e.target.value); setError(""); }}
/>
{error && <span style={{ color: "red" }}>{error}</span>}
<button type="submit">Sign Up</button>
</form>
);
}
4. Hands-On Practice Challenges
Master controlled components, state reducers, and validation pipelines with these interactive exercises. Click "Solve Challenge" to open the code workspace!
Challenge 1: Unified Form Change Reducer
Write a pure function handleFormChange(prevState, eventTarget) that accepts the current form state and an event target object (name, value, type, checked) and returns the updated immutable state.
- Extract
name,value,type, andcheckedfromeventTarget. - If
type === 'checkbox', storechecked; otherwise storevalue. - Spread previous state to maintain immutability.
{ username: "anshuman_singh", acceptTerms: true }
handleFormChange using computed property names [name].
Challenge 2: Multi-Rule Form Validator
Implement a validation validator validateRegistration(formData) that verifies: username $\ge 3$ chars, valid email format, password $\ge 8$ chars, and agreeToTerms === true. Return { isValid, errors }.
- Check username length $\ge 3$.
- Check email with RegExp format validation.
- Check password length $\ge 8$ and agreeToTerms is checked.
- Return
isValid: booleanand dictionary of error messages.
{ isValid: false, errors: { username: "...", email: "...", password: "...", agreeToTerms: "..." } }
Challenge 3: Form Data URL-Encoder / Serializer
Write a utility serializeToFormData(dataObject) that takes a controlled component form state object and converts it into a standard URL-encoded form submission string (e.g. username=anshuman&role=lead%20dev).
- Iterate through keys and values in
dataObject. - Use
encodeURIComponentfor safe URL encoding. - Join with ampersand
&delimiters.
username=anshuman&role=lead%20dev&newsletter=true