Forms and Controlled Components
Introduction
In React, most form inputs are controlled: their value comes from state and onChange updates that state. This chapter covers text fields, checkboxes, radio groups, selects, submit handling, and simple validation—without a form library.
Prerequisites
Controlled Text Inputs
Code explanation:
value+onChangekeep React state as the single source of truth- Use
htmlFor/idto link labels—Vue usesforin templates; JSX useshtmlFor - Trim on submit:
setEmail(e.target.value.trim())or validate before POST
Compare Vue v-model in Forms and v-model.
Submit Handler
Code explanation:
e.preventDefault()stops full page reload- Show field-level or form-level errors with conditional JSX
Checkbox and Radio
Single checkbox:
const [agreed, setAgreed] = useState(false);
<label>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
I agree to the terms
</label>Multiple checkboxes (array):
Radio group:
Select
const [country, setCountry] = useState("us");
<select
value={country}
onChange={(e) => setCountry(e.target.value)}
>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
</select>Uncontrolled Inputs (When Needed)
Use useRef for file inputs or integrating non-React widgets:
import { useRef } from "react";
const fileRef = useRef<HTMLInputElement>(null);
<input ref={fileRef} type="file" accept="image/*" />;
// Read on submit: fileRef.current?.files?.[0]Most text fields should stay controlled for predictable validation UI.
One Object for Form State
type FormState = { email: string; password: string };
const [form, setForm] = useState<FormState>({ email: "", password: "" });
function updateField<K extends keyof FormState>(key: K, value: FormState[K]) {
setForm((f) => ({ ...f, [key]: value }));
}
<input
value={form.email}
onChange={(e) => updateField("email", e.target.value)}
/>Validation (Without a Library)
| Approach | Use |
|---|---|
HTML5 required, minLength, pattern | Quick client hints |
State errors object | Custom messages per field |
| Validate on submit | Simple login/signup forms |
Tip
Libraries Later
React Hook Form and Formik help large forms—learn controlled patterns first.
FAQ
v-model equivalent?
value + onChange (or checked + onChange for checkboxes).
Input lags behind typing?
Do not reset value from props without syncing—avoid fighting controlled state.
Number input?
onChange={(e) => setAge(Number(e.target.value) || 0)}Or keep as string until submit.
Autofocus?
<input autoFocus /> // camelCase in JSX