Event Handling
16 minIntermediateReact
In this lesson
- ▸Attach handlers with
onClickandonChange - ▸Tell the difference between passing a function (
onClick={fn}) and calling it (onClick={fn()}) - ▸Pass event handlers down to child components as props
- ▸Read
e.target.valueand build a controlled input
The big idea
In React you don't call addEventListener. You pass a function as a prop like onClick, React calls it when the event happens, and your handler usually updates state.
In plain JavaScript you found an element and attached a listener. In React, you describe the listener right in the JSX, next to the element it belongs to. Event props are camelCase: onClick, onChange, onSubmit, onKeyDown.
Plain DOM
const btn = document.querySelector("#plus");
btn.addEventListener("click", () => {
cups = cups + 1;
render(); // you redraw by hand
});React
function addCup() {
setCups(cups + 1); // React re-renders
}
<button onClick={addCup}>+</button>Pass the function — don't call it
This is the most common React event bug. Remember that { } runs JavaScript while rendering. onClick={addCup} hands React the function to call later. onClick={addCup()} calls it right now, during render, and gives React whatever it returns (usually undefined). If that function sets state, you get an endless loop and React stops with "Too many re-renders".
Called during render
<button onClick={addCup()}>+</button>
<button onClick={setDrink("Kopi O")}>
Kopi O
</button>Called on click
<button onClick={addCup}>+</button>
<button onClick={() => setDrink("Kopi O")}>
Kopi O
</button>Need to pass an argument? Wrap the call in an arrow function: () => setDrink("Kopi O"). The arrow is created now; the call inside it runs on click.
The event object
React calls your handler with an event object — a wrapper around the browser's native event with the same familiar parts: e.target (the element), e.target.value (an input's current text), e.key, e.preventDefault().
function handleChange(e) {
console.log(e.target.value); // what's in the box right now
}
<input onChange={handleChange} />In React, onChange fires on every keystroke (like the DOM's input event), not only when the box loses focus.
Handlers as props
A child component often shouldn't own the state — the parent does. So the parent passes a function down as a prop, and the child calls it. By convention, the prop is named onSomething and the function handleSomething.
function DrinkButton({ label, onPick }) {
return <button onClick={onPick}>{label}</button>;
}
// in the parent:
<DrinkButton label="Kopi O" onPick={() => setDrink("Kopi O")} />Preview: controlled inputs
Give an input both value={note} and an onChange that updates note, and React state becomes the single source of truth for what's in the box. That's a controlled input. Because the value comes from state, clearing the state clears the box too. You'll use this pattern for every form later in the track.
value without onChange makes a read-only box: typing does nothing, and React warns in the console. Always pair them.
The + and - buttons do nothing yet. Connect them to addCup and removeCup with onClick — pass the functions, don't call them.
Then make sure the order can't go below 1 cup: change removeCup so it only decreases when cups is more than 1.
Checks
- + adds a cup
- - removes a cup
- It never goes below 1
Check understanding
What you learned
- ✓Event props are camelCase (
onClick,onChange) and take a function. - ✓
onClick={fn}passes it;onClick={fn()}calls it during render. Need arguments?onClick={() => fn(x)}. - ✓Parents pass handlers down as
onSomethingprops; children call them. - ✓
value+onChange(readinge.target.value) make a controlled input driven by state.
Console output from your code appears here.