Chapter 07 of 14 · browse all
Chapter 07

Handling Events

Responding to clicks, keys, and input in JSX, and the differences from plain DOM listeners.

Attaching a handler

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

function SaveButton() {
  function handleClick() {
    console.log("Saved");
  }

  return <button onClick={handleClick}>Save changes</button>;
}

Pass the function, do not call it. onClick={handleClick} is correct; onClick={handleClick()} runs it immediately during render and passes the return value.

Passing arguments

When a handler needs an argument, wrap it in an arrow function so the call happens at click time.

function ItemList({ items, onDelete }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.label}
          <button onClick={() => onDelete(item.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

The event object

React passes a synthetic event to your handler. It wraps the native browser event and normalises behaviour across browsers, with the same interface you already know.

function Form() {
  function handleSubmit(event) {
    event.preventDefault();
    console.log("Submitted without reloading");
  }

  function handleKeyDown(event) {
    if (event.key === "Escape") {
      console.log("Cancelled");
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input onKeyDown={handleKeyDown} />
      <button type="submit">Send</button>
    </form>
  );
}

preventDefault is needed often: forms reload the page by default, and links navigate away.

Common event props

  • Mouse: onClick, onDoubleClick, onMouseEnter, onMouseLeave
  • Keyboard: onKeyDown, onKeyUp
  • Form: onChange, onSubmit, onInput
  • Focus: onFocus, onBlur
  • Touch: onTouchStart, onTouchEnd

One difference is worth knowing: React's onChange on a text input fires on every keystroke, like the native input event, rather than only when the field loses focus.

Updating state from an event

function Toggle() {
  const [on, setOn] = useState(false);

  return (
    <button onClick={() => setOn(!on)}>
      {on ? "On" : "Off"}
    </button>
  );
}

Bubbling and stopping it

Events travel up through parent components, which is usually what you want. When a nested control should not trigger its container, stop propagation.

function Card({ onOpen }) {
  return (
    <div className="card" onClick={onOpen}>
      <h3>Report</h3>
      <button
        onClick={event => {
          event.stopPropagation();
          console.log("Menu only, card not opened");
        }}
      >
        More
      </button>
    </div>
  );
}

Handlers as props

Components you write receive functions as ordinary props. The convention is to name the prop onSomething and the function that implements it handleSomething.

function Toolbar({ onAdd, onRemove }) {
  return (
    <div>
      <button onClick={onAdd}>Add item</button>
      <button onClick={onRemove}>Remove item</button>
    </div>
  );
}

function App() {
  function handleAdd() { /* ... */ }
  function handleRemove() { /* ... */ }

  return <Toolbar onAdd={handleAdd} onRemove={handleRemove} />;
}