Skip to content
elephantoo

Responding to events

Lesson 5 of 30 13 min read

onClick and friends, passing handlers as props, event objects, preventDefault and propagation.


Interactive apps respond to people: clicks, typing, hovering, submitting forms, pressing keys. In React you attach event handlers directly in JSX. This lesson covers how to write them, pass them around, read event details, and control default behaviour and propagation.

Adding an event handler#

JSX
export default function Greeter() {
  function handleClick() {
    alert("Hello from React!");
  }

  return <button onClick={handleClick}>Say hi</button>;
}
  1. Define a function inside your component (by convention, handleSomething).
  2. Pass it to the element's event prop — onClick, onChange, onSubmit…

Event props are camelCase and take a function, not a string.

Pass the function, don't call it

JSX
<>
  <button onClick={handleClick}>✅ passes the function</button>
  <button onClick={handleClick()}>❌ calls it during every render</button>
  <button onClick={() => handleClick()}>✅ an inline arrow that calls it later</button>
</>

The second line runs handleClick immediately while rendering — and if the handler updates state, it causes an infinite render loop.

Inline handlers

Short handlers can be written inline:

JSX
<button onClick={() => console.log("clicked")}>Log</button>

Inline arrows are fine and idiomatic. Creating a new function per render is cheap.

Passing arguments#

Wrap the call in an arrow function:

JSX
function Toolbar() {
  function handleSelect(tool) {
    console.log(`Selected ${tool}`);
  }

  return (
    <div role="toolbar">
      {["pen", "eraser", "fill"].map((tool) => (
        <button key={tool} onClick={() => handleSelect(tool)}>
          {tool}
        </button>
      ))}
    </div>
  );
}

The event object#

React passes an event object to your handler. It wraps the browser's native event with the same interface (target, preventDefault(), key, clientX…), and works consistently across browsers:

JSX
function EventDetails() {
  function handlePointer(e) {
    console.log(e.type, e.clientX, e.clientY, e.shiftKey);
  }

  function handleKeyDown(e) {
    if (e.key === "Enter") console.log("Submitted:", e.currentTarget.value);
    if (e.key === "Escape") e.currentTarget.blur();
  }

  function handleChange(e) {
    console.log("Typed:", e.target.value);
  }

  return (
    <>
      <button onPointerDown={handlePointer}>Press me</button>
      <input onKeyDown={handleKeyDown} onChange={handleChange} placeholder="Type, then Enter" />
    </>
  );
}

e.nativeEvent gives you the underlying browser event if you ever need it.

Common events

CategoryProps
Mouse/pointeronClick, onDoubleClick, onPointerDown, onPointerEnter, onPointerLeave, onContextMenu
KeyboardonKeyDown, onKeyUp
FormsonChange, onInput, onSubmit, onReset, onInvalid
FocusonFocus, onBlur (these bubble in React, like focusin/focusout)
OtheronScroll, onWheel, onCopy, onPaste, onDragStart, onDrop, onLoad, onError

In React, onChange on an input fires on every keystroke (like the DOM's input event), not only when the field loses focus.

Passing handlers as props#

Most handlers live in a parent and are passed down. Name the prop after what happened (onPlay, onDelete) rather than the DOM event, so the component describes its own interactions:

JSX
function Button({ onClick, children }) {
  return (
    <button className="btn" onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movie, onPlay }) {
  return <Button onClick={() => onPlay(movie)}>▶ Play {movie}</Button>;
}

function MediaPlayer() {
  function handlePlay(movie) {
    alert(`Playing ${movie}`);
  }
  return (
    <>
      <PlayButton movie="Kiki's Delivery Service" onPlay={handlePlay} />
      <Button onClick={() => alert("Uploading…")}>Upload</Button>
    </>
  );
}

Button doesn't know what clicking means — that's decided by whoever uses it. This keeps components reusable.

Preventing default behaviour#

Some events have built-in browser behaviour. A <form> submission reloads the page; a link navigates. Call e.preventDefault() to cancel it:

JSX
function SearchForm({ onSearch }) {
  function handleSubmit(e) {
    e.preventDefault(); // stop the page reload
    const query = new FormData(e.currentTarget).get("q");
    onSearch(query);
  }

  return (
    <form onSubmit={handleSubmit} role="search">
      <label htmlFor="q">Search lessons</label>
      <input id="q" name="q" type="search" />
      <button type="submit">Search</button>
    </form>
  );
}

Listen for onSubmit on the form — it also fires when the user presses Enter. (React 19 also lets you pass a function to a form's action prop; that's covered in the React 19 lesson.)

Propagation: events bubble up#

Events travel up from the element that was clicked through all its ancestors. Both handlers below run when you click the button:

JSX
function Row({ lesson, onOpen, onDelete }) {
  return (
    <li onClick={() => onOpen(lesson.id)} className="row">
      {lesson.title}
      <button
        onClick={(e) => {
          e.stopPropagation(); // don't also open the lesson
          onDelete(lesson.id);
        }}
        aria-label={`Delete ${lesson.title}`}
      >
        🗑
      </button>
    </li>
  );
}
  • e.stopPropagation() stops the event reaching parent handlers.
  • e.preventDefault() cancels the browser's default action. They're unrelated — you might need either, both or neither.

To handle an event during the capture phase (before children), add Capture: onClickCapture.

Handlers can do anything — except mutate during render#

Event handlers are the right place for side effects: updating state, sending requests, navigating, logging. Rendering code (the body of your component) should stay pure. In the next lesson, handlers will update state, which makes React re-render with new values.

Accessibility: use the right element#

JSX
// ❌ A clickable div: no keyboard support, not announced as a button
const bad = <div onClick={save}>Save</div>;

// ✅ A real button: focusable, works with Enter/Space, announced correctly
const good = <button type="button" onClick={save}>Save</button>;

Use <button> for actions and <a href> (or a router Link) for navigation.

Common mistakes#

  • onClick={handleClick()} — calling instead of passing.
  • onclick (lowercase) — React ignores it and warns.
  • Returning false to prevent default — call e.preventDefault().
  • Using stopPropagation when you meant preventDefault, or vice versa.
  • Forgetting type="button" on buttons inside forms: the default type is submit.

What's next#

Handlers that only log aren't very exciting. Next, give components memory with state using useState.

Check your understanding

Quick quiz

0/3 answered
  1. 1.What is wrong with <button onClick={handleClick()}>?

  2. 2.How do you stop a form's onSubmit from reloading the page?

  3. 3.A <button> inside a clickable <li> triggers both click handlers. How do you stop the <li> handler from running?

Finished reading?

Mark this lesson complete to track your progress.