Handling Events
An interface is only useful when it reacts to the user: a click adds an item to the cart, pressing Enter sends a message, typing filters a list. In React you respond to these actions with event handlers, functions that React calls when an event happens.
In this lesson you will attach handlers to elements, pass arguments to them, use the event object, control how events travel up the page, and pass handlers to child components as props.
Your first event handler
Add an event handler by passing a function to a prop such as onClick. Event props use camelCase: onClick, onChange, onSubmit, onKeyDown, onMouseEnter, onFocus and so on.
import { createRoot } from "react-dom/client";
function MatchScore() {
function handleSixClick() {
console.log("Six! Kabir hits it over long-on.");
}
return (
<div>
<h3>Rohtak XI vs Jaipur Strikers</h3>
<button onClick={handleSixClick}>Record a six</button>
<button onClick={() => console.log("Wicket! Bowled him.")}>Record a wicket</button>
</div>
);
}
createRoot(document.getElementById("root")).render(<MatchScore />);Clicking "Record a six" and then "Record a wicket" prints:
Six! Kabir hits it over long-on. Wicket! Bowled him.
The first button uses a named function defined inside the component. The second uses an inline arrow function, which is handy for short handlers. By convention, handler functions are named handle plus the event or action: handleSixClick, handleSubmit, handleSearchChange.
Because the handler is defined inside the component, it can read the component's props and state. That is exactly why React components are functions: the markup, data and behaviour live together.
Pass the function, do not call it
This is the most common beginner mistake with events:
<button onClick={handleSixClick}>Six</button> // correct: passes the function
<button onClick={handleSixClick()}>Six</button> // wrong: calls it during renderWith brackets, handleSixClick() runs immediately while React is rendering, and its return value (undefined) becomes the click handler. Nothing happens on click. If the handler changed state, calling it during render could even cause an endless loop of re-renders.
When you need to pass an argument, wrap the call in an arrow function so it runs only on click: onClick={() => addToCart("Bat")}.
Arguments and the event object
React passes an event object as the first argument to your handler. It works like the browser's normal event: e.target is the element where the event started, e.key tells you which key was pressed, and e.preventDefault() stops the browser's default action. React wraps the browser event in its own object so it behaves the same in every browser; the original is available as e.nativeEvent.
import { createRoot } from "react-dom/client";
const products = [
{ id: 1, name: "Cricket ball", price: 450 },
{ id: 2, name: "Wicket keeping gloves", price: 1899 },
];
function Shop() {
function addToCart(product) {
console.log(`Added ${product.name} (₹${product.price})`);
}
function handleKeyDown(e) {
if (e.key === "Enter") {
console.log(`Searching for "${e.target.value}"`);
}
}
return (
<div>
<input placeholder="Search and press Enter" onKeyDown={handleKeyDown} />
{products.map((p) => (
<p key={p.id}>
{p.name} <button onClick={() => addToCart(p)}>Add</button>
</p>
))}
</div>
);
}
createRoot(document.getElementById("root")).render(<Shop />);Typing "bat" into the box, pressing Enter, and then clicking both "Add" buttons prints:
Searching for "bat" Added Cricket ball (₹450) Added Wicket keeping gloves (₹1899)
Each button gets its own small arrow function that remembers which product it belongs to. If you need both your own argument and the event, accept the event in the arrow function and pass it on: onClick={(e) => addToCart(p, e)}.
Events bubble up
When you click a button inside a card, the click event first fires on the button and then bubbles up through every parent element, so a parent's onClick runs too. Call e.stopPropagation() in the child's handler when the parent should not react.
import { createRoot } from "react-dom/client";
function OrderCard() {
return (
<div
onClick={() => console.log("Card: open order details")}
style={{ border: "1px solid #999", padding: 12, cursor: "pointer" }}
>
<p>Order #2207: 2 x Veg thali</p>
<button onClick={() => console.log("Button: track order")}>Track</button>{" "}
<button
onClick={(e) => {
e.stopPropagation();
console.log("Button: cancel order");
}}
>
Cancel
</button>
</div>
);
}
createRoot(document.getElementById("root")).render(<OrderCard />);Clicking "Track" and then "Cancel" prints:
Button: track order Card: open order details Button: cancel order
The "Track" click bubbles to the card, so the card's handler also runs. "Cancel" stops propagation, so only its own handler runs. Similarly, e.preventDefault() stops a browser default, such as a form reloading the page on submit; you will use it in Forms & Controlled Inputs. All React events bubble except onScroll.
Passing handlers as props
Often the component that shows a button is not the one that knows what to do. The parent passes a function down as a prop, and the child calls it. Props for handlers are named on plus the action, just like built-in events:
function PlayerRow({ name, onSelect }) {
return (
<li>
{name} <button onClick={() => onSelect(name)}>Pick</button>
</li>
);
}
function TeamPicker() {
function handleSelect(name) {
console.log(`${name} is in the playing XI`);
}
return (
<ul>
<PlayerRow name="Rohan" onSelect={handleSelect} />
<PlayerRow name="Farhan" onSelect={handleSelect} />
</ul>
);
}PlayerRow does not care what happens after "Pick"; it only reports the event upward. The parent decides. This keeps child components reusable: the same row could be used on a different page with a different onSelect. When the parent needs to remember the choice, it stores it in state, which is the next lesson.
Common mistakes
- Calling instead of passing.
onClick={save()}runs during render. WriteonClick={save}oronClick={() => save(id)}. - Lowercase event names.
onclickdoes not work in JSX; useonClick. - Forgetting that events bubble. A button inside a clickable card triggers both handlers unless you call
e.stopPropagation(). - Changing the DOM in a handler. Do not set
e.target.styleortextContentby hand; update state and let React render the change.
Key points
- Attach handlers with camelCase props such as
onClick,onChangeandonKeyDown. - Pass the function itself; use an arrow function when you need arguments.
- Handlers receive an event object with
target,key,preventDefault()andstopPropagation(). - Events bubble from child to parent;
stopPropagation()stops them. - Pass handlers to children as
on*props so the parent decides what happens. - Event handlers are the right place for side effects such as logging or saving.
Exercise
Build a Keypad component for a lift with buttons for floors 0 to 5 (use map). Clicking a button logs "Going to floor N". Also add an input where pressing the Escape key logs "Emergency stop!". Make the floor buttons call a handler that receives the floor number as an argument.
Show solution
import { createRoot } from "react-dom/client";
function Keypad() {
const floors = [0, 1, 2, 3, 4, 5];
function goTo(floor) {
console.log(`Going to floor ${floor}`);
}
function handleKeyDown(e) {
if (e.key === "Escape") {
console.log("Emergency stop!");
}
}
return (
<div>
{floors.map((f) => (
<button key={f} onClick={() => goTo(f)}>{f}</button>
))}
<p><input placeholder="Press Esc here" onKeyDown={handleKeyDown} /></p>
</div>
);
}
createRoot(document.getElementById("root")).render(<Keypad />);Each button wraps goTo(f) in an arrow function, so the call happens only on click, with the right floor number.
Quick Quiz
1. Which line correctly attaches a click handler?
React uses camelCase onClick, and you pass the function without calling it.
2. How do you pass the product id to a handler on click?
The arrow function delays the call until the click happens.
3. A button inside a clickable div should not trigger the div's handler. What do you call?
stopPropagation() stops the event bubbling to parent elements.
4. What is a good name for a prop that receives a "delete" handler?
Handler props follow the on* convention like built-in events; the function inside the parent is usually named handle*.
Learn React with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
