Events and forms
Passing a function rather than calling it, reading what the user typed, and the controlled-input pattern that keeps the page and the state in step.
A handler is a value, not a call
An event prop takes a function: onClick={handleClick}. Writing onClick={handleClick()} calls it during render and passes its result — usually undefined — which is the single most common React mistake, and it usually fails quietly.
When the handler needs an argument, wrap it in an arrow: onClick={() => remove(item.id)}. The arrow is a function value; remove(item.id) runs only when the click happens. Event handlers are where the impure work lives — updating state, clearing a field, calling the setter from last chapter.
Controlled inputs
A text input normally keeps its own value inside the DOM. A controlled input hands that job to React: value={name} shows what the state says, and onChange writes back what was typed. The state is the single source of truth, and the page cannot drift from it.
The event object carries what happened: event.target.value is the input's current text. So the pair is value={name} with onChange={(e) => setName(e.target.value)} — read up, write down. Drop the onChange and the field becomes read-only; drop the value and it is uncontrolled again, holding text the state knows nothing about.
Submitting without the page reloading
A <form> submits the old way by default: the browser navigates and the page is gone. In React the submit handler stops that with event.preventDefault(), then does the real work — validating, saving, clearing fields.
A <select> is controlled the same way: value plus onChange. Because the state holds the answer, the code can read it anywhere — in the submit handler, in a summary next to the form, in the next chapter's preview.
What the event object carries
React hands your handler a synthetic event: an object with the same fields as the browser event — target, type, key, preventDefault. event.target is the element that fired it, and event.target.value is that element’s current value, which is why one handler can serve several inputs: read the field’s name from event.target.name and write the value under it.
The name of the prop is the event name with on in front and a capital: onClick, onChange, onSubmit, onKeyDown. A lowercase onclick is passed through as an attribute and never called.
A form with several fields
Several fields can share one state object: const [form, setForm] = useState({ name: "", email: "" }), and one handler updates the field named by the input: setForm({ ...form, [e.target.name]: e.target.value }). The spread keeps the fields the handler is not touching, and the computed key writes the one it is.
The same handler works for a form with ten fields, and it does not change as fields are added — only the state object and the inputs do.
The controlled pattern, twice
const { useState } = React;
function Signup() {
const [name, setName] = useState('');
const [topic, setTopic] = useState('jsx');
function handleSubmit(event) {
event.preventDefault();
// The fields know what to clear because the state owns them.
setName('');
setTopic('jsx');
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Name</label>
<input id="name" value={name} onChange={(e) => setName(e.target.value)} />
<label htmlFor="topic">Topic</label>
<select id="topic" value={topic} onChange={(e) => setTopic(e.target.value)}>
<option value="jsx">JSX</option>
<option value="state">State</option>
</select>
<button type="submit">Clear</button>
<p>Typing: {name === '' ? 'nothing yet' : name} ({topic})</p>
</form>
);
}Every keystroke is a render
A controlled input writes to state on every change, so a keystroke is a render: React calls the component, the input receives the new value, and the cursor stays where the browser put it. That sounds expensive and is usually invisible, because a small tree renders in well under a frame.
The benefit is that the value is available as ordinary JavaScript immediately. A counter can update, a preview can follow, a submit button can disable itself — no reading from the DOM, no second copy to keep in step.
Inputs that are not text
Checkboxes and radios are controlled with checked, not value, and their handler updates a boolean. A number input still gives you a string from event.target.value; convert it when you use it, and decide what an empty field means before you do arithmetic.
A <textarea> takes its content as children in HTML, but in React it is controlled with value like any other field. A <select> uses value on the select itself and value on each option; the option text is what the reader sees, and its value is what the state receives.
Three form mistakes: giving value without onChange, which makes the field read-only and logs a warning; using value on a checkbox instead of checked; and forgetting event.preventDefault() in the submit handler, so the browser navigates and the state disappears with the page.
Try these next
- Add a textarea for notes and clear it with the other fields on submit.
- Disable the submit button while the name is empty, then re-enable it by typing.
- Convert the guests field to a number and show an error message when it is below one.
A form that belongs to React
Run it, type in both fields, and submit. Then add a third field — a checkbox for "send me the weekly note" — with its own state, and have the submit handler clear it too.
A booking form with a checkbox
Run it, fill the fields and submit. Then add a textarea for notes, keep it controlled, and clear it with the other fields after a successful submission.
Read a controlled input's state in one place and the whole form becomes testable: the values are plain JavaScript the moment they are typed, not something hidden in the DOM.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Why is onClick={handleClick()} wrong?
