Forms and Controlled Inputs
Everyday form field patterns: controlled values, submit handling, validation display, and accessibility hooks.
Busque em todas as páginas da documentação
Everyday form field patterns: controlled values, submit handling, validation display, and accessibility hooks.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Wire value to state and update it in onChange. React becomes the source of truth for the field.
const [email, setEmail] = useState("");
return (
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
);Use checked plus onChange for booleans. Do not mix defaultChecked with controlled mode.
const [agree, setAgree] = useState(false);
return (
<input
type="checkbox"
checked={agree}
onChange={(e) => setAgree(e.target.checked)}
/>
);Bind value on <select> to state; read e.target.value in onChange.
const [role, setRole] = useState("viewer");
return (
<select value={role} onChange={(e) => setRole(e.target.value)}>
<option value="viewer">Viewer</option>
<option value="editor">Editor</option>
</select>
);Same controlled contract as text inputs - value and onChange on <textarea>.
const [bio, setBio] = useState("");
return <textarea value={bio} onChange={(e) => setBio(e.target.value)} rows={4} />;One state object works when fields submit together. Spread previous state on each field change.
const [form, setForm] = useState({ name: "", email: "" });
const setField = (key: keyof typeof form, value: string) =>
setForm((f) => ({ ...f, [key]: value }));defaultValue sets the initial DOM value; read it later via a ref when you do not need per-keystroke React state.
const inputRef = useRef<HTMLInputElement>(null);
return <input ref={inputRef} defaultValue="initial" />;Handle submit on the form, call preventDefault, then run async work. Prefer form-level submit over button-only handlers.
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
await save(form);
}
return <form onSubmit={onSubmit}>{/* fields */}</form>;File inputs stay mostly uncontrolled. Read e.target.files on change; you cannot set value for security reasons.
function onFile(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (file) setUpload(file);
}
return <input type="file" onChange={onFile} />;Share the same state string across radios; each option compares to its own value.
const [plan, setPlan] = useState("free");
return (
<>
<label><input type="radio" name="plan" value="free" checked={plan === "free"} onChange={() => setPlan("free")} /> Free</label>
<label><input type="radio" name="plan" value="pro" checked={plan === "pro"} onChange={() => setPlan("pro")} /> Pro</label>
</>
);Derive validity during render and disable the submit control until the form is ready.
const isValid = email.includes("@") && password.length >= 8;
return (
<button type="submit" disabled={!isValid || isPending}>
Create account
</button>
);Show errors next to the field and wire aria-invalid / aria-describedby for assistive tech.
return (
<>
<input aria-invalid={!!error} aria-describedby="email-err" value={email} onChange={...} />
{error && <p id="email-err">{error}</p>}
</>
);Set controlled state back to initials, or remount the form with a new key for a full reset including children.
function onReset() {
setForm({ name: "", email: "" });
}Keep the raw string while typing if needed, or parse carefully so empty input does not become NaN.
const [qty, setQty] = useState(1);
onChange={(e) => {
const n = Number(e.target.value);
if (!Number.isNaN(n)) setQty(n);
}}Keep the input snappy with immediate state; debounce the expensive filter or fetch separately.
const [query, setQuery] = useState("");
const debounced = useDebouncedValue(query, 300);
useEffect(() => {
search(debounced);
}, [debounced]);Associate labels with controls via matching htmlFor and id (or wrap the control inside the label).
const id = useId();
return (
<>
<label htmlFor={id}>Email</label>
<input id={id} type="email" value={email} onChange={...} />
</>
);Stack versions: React 19 · TypeScript (strict) · accessible form controls
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥