Form Inputs and Controlled Values
A browser input already knows how to accept text. React state becomes useful when the rest of the interface needs the current value during rendering.
function ReaderName() {
const [name, setName] = useState("");
return (
<label>
Reader name
<input
value={name}
onChange={(event) => setName(event.target.value)}
/>
</label>
);
}The input receives its displayed value from state. Every edit reports the next browser value through onChange. The handler requests that next state, React renders again, and the input receives the new value prop.
This is a controlled input. Its current value comes from React state.
Follow One Typed Character
The first render passes an empty string.
state name is ""
input receives value=""The reader types R. The browser prepares the edited value and React calls the change handler.
event.target.value is "R"
handler calls setName("R")
React renders with name "R"
input receives value="R"The cycle repeats for each edit.
React's onChange for text inputs runs as the value changes. It does not wait for the field to lose focus as the native DOM change event often does for text controls.
A controlled input displays the value or checked supplied by the current React render.
The handler must update the state synchronously from the new value. If it delays or transforms the wrong value, React can restore stale text on the next render.
Pair value with onChange
This input receives a fixed value and no update path.
<input value={name} />Typing cannot persist because every render instructs the input to display name. React reports a read-only-field warning in development.
Add the handler.
<input
value={name}
onChange={(event) => setName(event.target.value)}
/>Or declare an explicitly read-only input.
<input value={generatedId} readOnly />Use readOnly when the user should select or copy a value but not edit it. Use plain text when the value does not need input semantics.
Initialize Controlled Text with a String
The value prop for a controlled text input should remain a string throughout the input's lifetime.
const [name, setName] = useState("");This initialization is risky.
const [name, setName] = useState(undefined);The first render leaves the input uncontrolled because no value is supplied. A later string turns it into a controlled input, and React reports the switch.
Normalize optional source data before supplying it.
const [name, setName] = useState(savedName ?? "");Do not pass null as the controlled value. Use an empty string for an empty text field.
An input should not switch between controlled and uncontrolled during its mounted lifetime.
Uncontrolled Inputs Let the DOM Retain the Value
Use defaultValue for an initial value that the DOM will manage afterward.
<input name="readerName" defaultValue="Ada" />React supplies Ada at initialization. Later typing remains in the DOM input without a state update for every character.
Changing the defaultValue prop after mount does not reset the current typed value. The prop describes the initial uncontrolled value.
Read uncontrolled form values on submission through FormData or through a ref for a specific imperative case.
function handleSubmit(event) {
event.preventDefault();
const data = new FormData(event.currentTarget);
console.log(data.get("readerName"));
}Controlled and uncontrolled inputs are both supported. Use controlled state when rendering, validation, or sibling UI needs the current value. Let the DOM manage it when the value is collected only at submission and no live React calculation depends on it.
Text Input Values Are Strings
The browser reports text-like input values as strings, including type="number" through event.target.value.
const [weeklyGoal, setWeeklyGoal] = useState("3");<input
type="number"
value={weeklyGoal}
onChange={(event) => setWeeklyGoal(event.target.value)}
/>Keeping the raw string supports editing states such as an empty field, -, or a partially entered decimal when allowed. Converting every keystroke to a number can erase those temporary states.
Convert for a calculation.
const numericGoal = Number(weeklyGoal);
const validGoal = Number.isInteger(numericGoal) && numericGoal > 0;event.target.valueAsNumber can read a number from a number input, but an empty or invalid value produces NaN. The component still needs an invalid-value policy.
Checkboxes Use checked
A checkbox expresses its current on or off state through checked, not value.
function CompletedFilter() {
const [showCompleted, setShowCompleted] = useState(true);
return (
<label>
<input
type="checkbox"
checked={showCompleted}
onChange={(event) => setShowCompleted(event.target.checked)}
/>
Show completed lessons
</label>
);
}event.target.checked is a boolean. event.target.value is a separate form submission value and usually remains the string on unless another value prop is supplied.
For an uncontrolled checkbox, use defaultChecked.
<input type="checkbox" defaultChecked />Do not use selected or value to control a checkbox's checked state.
Radio Buttons Share One Selected Value
A radio group represents one selection from several controls with the same name.
const [status, setStatus] = useState("all");<label>
<input
type="radio"
name="status"
value="all"
checked={status === "all"}
onChange={(event) => setStatus(event.target.value)}
/>
All lessons
</label>Another control uses the same name and another value.
<input
type="radio"
name="status"
value="complete"
checked={status === "complete"}
onChange={(event) => setStatus(event.target.value)}
/>State stores the selected group value. Each radio calculates whether that value equals its own value.
The shared name also gives the browser the correct group behavior for keyboard selection and form submission.
Select Controls Use value on select
Control a select at the select element.
function ChapterFilter() {
const [chapter, setChapter] = useState("all");
return (
<select
value={chapter}
onChange={(event) => setChapter(event.target.value)}
>
<option value="all">All chapters</option>
<option value="jsx">JSX</option>
<option value="state">State</option>
</select>
);
}Do not set selected on an option in React source.
<option value="jsx" selected>JSX</option>The select value determines which matching option is selected.
For multiple selection, add multiple and pass an array of selected option values.
<select multiple value={selectedChapters}>Multiple-select change handling needs to read the selected options collection. The first app uses one selected value.
Textareas Also Use value
HTML source often places initial text inside a textarea. React controls it through value.
const [note, setNote] = useState("");<textarea
value={note}
onChange={(event) => setNote(event.target.value)}
/>Use defaultValue for an uncontrolled textarea.
<textarea defaultValue="Add a note" />Do not place normal children inside a React textarea to establish its value.
File Inputs Remain User Controlled
The browser restricts assigning a file path to a file input. React cannot control its file selection through a normal value string.
<input type="file" onChange={handleFileChange} />Read the selected FileList from the event.
function handleFileChange(event) {
const file = event.target.files?.[0];
if (!file) return;
console.log(file.name);
}The user and browser control the selected file value. Uploading, type verification, size limits, previews, and server handling are separate operations performed after selection.
Use Separate State for a Small Form
Three fields can have three direct state positions.
const [name, setName] = useState("");
const [weeklyGoal, setWeeklyGoal] = useState("3");
const [sendReminder, setSendReminder] = useState(false);Each handler updates one value. The source makes the type and purpose of each state position visible.
An object can group fields when many operations update them together.
const [form, setForm] = useState({
name: "",
weeklyGoal: "3",
});Update it by replacement.
setForm((current) => ({
...current,
name: nextName,
}));This replacement loses weeklyGoal.
setForm({ name: nextName });React state setters do not merge objects automatically. The spread retains unchanged top-level fields.
For the milestone, separate state remains easier to inspect.
Submit Through the Form Event
Handle submission on the form.
function handleSubmit(event) {
event.preventDefault();
console.log({ name, weeklyGoal });
}<form onSubmit={handleSubmit}>
{/* labeled controls */}
<button type="submit">Save goal</button>
</form>The submit event includes button clicks and keyboard submission. preventDefault keeps this client-only example from performing a browser submission and document load.
Logging is a temporary milestone action. A production form needs a storage or server mutation path, pending state, error feedback, and server-side validation.
Preserve Browser Validation
Use native constraints where they represent the field.
<input
id="email"
name="email"
type="email"
required
value={email}
onChange={(event) => setEmail(event.target.value)}
/>The browser can prevent invalid submission and provide platform feedback. React state can also calculate inline guidance.
const emailLooksValid = email.includes("@");Client checks improve interaction but do not establish trust. Submitted values can bypass the browser and must be validated by the receiving server.
When showing a custom error, connect it to the field.
<input aria-describedby="email-error" aria-invalid={!emailLooksValid} />
<p id="email-error">Enter a complete email address.</p>Render the error only when the product's validation timing says it should be shown. Reporting an error before the reader begins typing can create noisy form behavior.
Build the Reading Goal Form
Use controlled text and checkbox state.
function ReadingGoalForm() {
const [name, setName] = useState("");
const [weeklyGoal, setWeeklyGoal] = useState("3");
const [sendReminder, setSendReminder] = useState(false);
// return form
}Each control receives its current state and change handler. The form submit handler reads the same render snapshot.
function handleSubmit(event) {
event.preventDefault();
console.log({ name, weeklyGoal, sendReminder });
}Check the Form State
Each form control now reads one current value from state.
controlled text -> value string in React state
controlled checkbox -> checked boolean in React state
controlled radio group -> one selected value in React state
controlled select -> value on the select element
uncontrolled field -> current value retained by the DOM
file input -> selected files controlled by user and browserSource Notes
- React DOM reference,
input - React DOM reference,
select - React DOM reference,
textarea - React DOM reference,
form - MDN, Client-side form validation