Handling Events

React · Lesson 9 of 29 6 min read Updated

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.

React JSXTry it yourself
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:

Output
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:

React JSX
<button onClick={handleSixClick}>Six</button>     // correct: passes the function
<button onClick={handleSixClick()}>Six</button>   // wrong: calls it during render

With 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.

React JSXTry it yourself
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:

Output
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.

React JSXTry it yourself
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:

Output
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:

React JSX
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. Write onClick={save} or onClick={() => save(id)}.
  • Lowercase event names. onclick does not work in JSX; use onClick.
  • 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.style or textContent by hand; update state and let React render the change.

Key points

  • Attach handlers with camelCase props such as onClick, onChange and onKeyDown.
  • Pass the function itself; use an arrow function when you need arguments.
  • Handlers receive an event object with target, key, preventDefault() and stopPropagation().
  • 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
React JSX
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?

2. How do you pass the product id to a handler on click?

3. A button inside a clickable div should not trigger the div's handler. What do you call?

4. What is a good name for a prop that receives a "delete" handler?

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