Runnable lesson · press Try it on the examples to edit and run them here.
Different inputs store their value in different ways. This lesson gives you a reliable recipe for each.
Text, textarea, select: value
function App() {
const [country, setCountry] = React.useState("uk");
const [message, setMessage] = React.useState("");
return (
<form onSubmit={(e) => e.preventDefault()}>
<label>
Country
<select aria-label="Country" value={country} onChange={(e) => setCountry(e.target.value)}>
<option value="uk">United Kingdom</option>
<option value="us">United States</option>
<option value="in">India</option>
</select>
</label>
<label>
Message
<textarea aria-label="Message" value={message} onChange={(e) => setMessage(e.target.value)} />
</label>
<p>Country: {country}</p>
<p>Message: {message}</p>
</form>
);
}
Notes:
- On
<select>, setvalueon the select, notselectedon options. - A
<textarea>takes its text from thevalueprop (in HTML it takes children).
One checkbox: checked
function App() {
const [dark, setDark] = React.useState(false);
return (
<div>
<label>
<input type="checkbox" checked={dark} onChange={(e) => setDark(e.target.checked)} />
Dark mode
</label>
<p>Dark mode is {dark ? "on" : "off"}</p>
</div>
);
}
A group of checkboxes: an array
When several boxes make one answer, store the chosen values in an array and add or remove on change:
function App() {
const options = ["React", "CSS", "SQL"];
const [chosen, setChosen] = React.useState([]);
function toggle(option) {
setChosen(chosen.includes(option) ? chosen.filter((o) => o !== option) : [...chosen, option]);
}
return (
<fieldset>
<legend>Skills</legend>
{options.map((option) => (
<label key={option} style={{ display: "block" }}>
<input type="checkbox" checked={chosen.includes(option)} onChange={() => toggle(option)} />
{option}
</label>
))}
<p>Chosen: {chosen.length ? chosen.join(", ") : "none"}</p>
</fieldset>
);
}
Radio buttons: one value, compared with checked
All radio buttons in a group share a name. Each one is checked when the state equals its own value:
function App() {
const [plan, setPlan] = React.useState("free");
const plans = [
{ value: "free", label: "Free" },
{ value: "pro", label: "Pro" },
];
return (
<fieldset>
<legend>Plan</legend>
{plans.map((p) => (
<label key={p.value} style={{ display: "block" }}>
<input type="radio" name="plan" value={p.value} checked={plan === p.value} onChange={(e) => setPlan(e.target.value)} />
{p.label}
</label>
))}
<p>Plan: {plan}</p>
</fieldset>
);
}
fieldset and legend give the group a name that screen readers announce.
Multiple select
<select multiple> takes an array as value, and you collect the selected options:
function App() {
const [picked, setPicked] = React.useState(["b"]);
function handleChange(e) {
setPicked(Array.from(e.target.selectedOptions, (o) => o.value));
}
return (
<div>
<select multiple aria-label="Letters" value={picked} onChange={handleChange} style={{ height: 80 }}>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
<p>Selected: {picked.join(", ")}</p>
</div>
);
}
For many selections, a group of checkboxes is often easier to use than a multiple select.
Quick reference
| Input | State holds | Props |
|---|---|---|
| text, email, password, number, date | string | value, onChange |
| textarea | string | value, onChange |
| select | string | value on the select, onChange |
| one checkbox | boolean | checked, onChange (use e.target.checked) |
| checkbox group | array | each checked={arr.includes(x)} |
| radio group | string | each checked={state === value}, same name |
| file | (uncontrolled) | read e.target.files |
Key points
- Text inputs, textarea and select use
value; checkboxes and radios usechecked. - Checkbox groups store an array; radio groups store one value.
- Group related inputs in a
fieldsetwith alegend.