React course menu
Handling Events in React: onClick, onChange and More
Respond to user actions in React: event handler props like onClick and onChange, passing functions instead of calling them, passing arguments, the event object, preventDefault for forms, event propagation and stopPropagation, and naming handler props.
On this page
Events let users interact with your app: clicking, typing, submitting, hovering. In React you attach event handlers with camelCase props such as onClick, onChange, onSubmit, onMouseEnter and onKeyDown.
Your first handler#
import { useState } from "react";
export default function App() {
const [message, setMessage] = useState("Nothing clicked yet");
function handleSave() {
setMessage("Saved at 10:30");
}
return (
<div>
<button onClick={handleSave}>Save</button>
<button onClick={() => setMessage("Cancelled")}>Cancel</button>
<p>{message}</p>
</div>
);
}You can pass a named function (handleSave) or write an inline arrow function. The common naming convention is handleSomething for the function and onSomething for the prop.
Pass the function, do not call it#
<button onClick={handleSave}> // ✅ React calls it when clicked
<button onClick={handleSave()}> // ❌ calls it immediately, during renderingWith parentheses, the function runs while the component renders, not on click. If it also updates state, that triggers another render and an endless loop ("Too many re-renders").
Passing arguments#
Wrap the call in an arrow function:
import { useState } from "react";
const sizes = ["S", "M", "L", "XL"];
export default function App() {
const [selected, setSelected] = useState("M");
return (
<div>
{sizes.map((size) => (
<button
key={size}
onClick={() => setSelected(size)}
style={{ marginRight: 6, fontWeight: size === selected ? "bold" : "normal" }}
>
{size}
</button>
))}
<p>Selected size: {selected}</p>
</div>
);
}The event object#
Handlers receive an event object with details about what happened: e.target (the element), e.key for keyboard events, mouse position, and more:
import { useState } from "react";
export default function App() {
const [log, setLog] = useState("Type in the box and press Enter");
return (
<div>
<input
placeholder="Search..."
onKeyDown={(e) => {
if (e.key === "Enter") setLog(`You searched for "${e.target.value}"`);
}}
/>
<p>{log}</p>
</div>
);
}React's event objects work the same way across browsers and have the same main properties and methods as native DOM events.
Preventing default behaviour#
Some events have built-in browser behaviour; a form submit reloads the page. Call e.preventDefault() to stop it and handle the submission in JavaScript:
import { useState } from "react";
export default function App() {
const [result, setResult] = useState("");
function handleSubmit(e) {
e.preventDefault();
const data = new FormData(e.target);
setResult(`Subscribed: ${data.get("email")}`);
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required placeholder="you@example.com" />
<button type="submit">Subscribe</button>
<p>{result}</p>
</form>
);
}The Forms lesson shows the usual React approach of keeping input values in state.
Propagation: events bubble up#
An event on a child also triggers handlers on its parents ("bubbling"). e.stopPropagation() stops it going further:
import { useState } from "react";
export default function App() {
const [log, setLog] = useState([]);
const add = (text) => setLog((prev) => [...prev, text]);
return (
<div>
<div onClick={() => add("card clicked")} style={{ border: "1px solid #ccc", padding: 12, cursor: "pointer" }}>
Product card
<button onClick={() => add("like clicked")} style={{ marginLeft: 8 }}>♥ Like (bubbles)</button>
<button
onClick={(e) => {
e.stopPropagation();
add("share clicked");
}}
style={{ marginLeft: 8 }}
>
Share (stops)
</button>
</div>
<button onClick={() => setLog([])} style={{ marginTop: 8 }}>Clear log</button>
<ol>{log.map((entry, i) => <li key={i}>{entry}</li>)}</ol>
</div>
);
}Clicking Like logs "like clicked" and then "card clicked"; clicking Share logs only "share clicked". (Index keys are fine here: the log only ever grows at the end.)
Custom event props on your components#
Your own components can accept handler props with any name, which lets parents decide what happens:
import { useState } from "react";
function LikeButton({ liked, onToggle }) {
return <button onClick={onToggle}>{liked ? "♥ Liked" : "♡ Like"}</button>;
}
export default function App() {
const [liked, setLiked] = useState(false);
return <LikeButton liked={liked} onToggle={() => setLiked(!liked)} />;
}Common mistakes#
Practice#
Practice exercise
Build a colour picker: three buttons "Red", "Green" and "Blue". Clicking one changes the background colour of a box below them and shows the chosen name.
Show hint
Keep the chosen colour in useState, map over an array of colour names to create the buttons, and use style={{ background: colour }} on the box.
Show solution
import { useState } from "react";
const colours = ["red", "green", "blue"];
export default function App() {
const [colour, setColour] = useState("red");
return (
<div>
{colours.map((c) => (
<button key={c} onClick={() => setColour(c)} style={{ marginRight: 6, textTransform: "capitalize" }}>
{c}
</button>
))}
<div style={{ background: colour, height: 60, marginTop: 8 }} />
<p>Chosen: {colour}</p>
</div>
);
}Key takeaway#
Next: State with useState.
Finished this lesson?