Events

Attach handlers, pass them arguments, and control what the browser does next.

08of 20
≈ 10Min read
8Parts

Handlers are props

camelCase, and a function — not a string
<button onClick={handleClick}>Save</button>

// HTML was a string of code
<button onclick="handleClick()">Save</button>
The parentheses mistake

onClick={handleClick()} calls the function while rendering and passes its return value as the handler. The symptom is famous: the action happens immediately on load, once per render, and nothing happens on click.

Passing arguments

A new arrow function on every render is fine. It is one of the cheapest things in a render, and worrying about it before measuring is the classic premature optimisation in React.

wrap it in an arrow
<button onClick={() => remove(door.id)}>Remove</button>

// the arrow is the handler; remove runs only when it is clicked
// with the event as well:
<button onClick={(e) => remove(door.id, e.shiftKey)}>Remove</button>

Where to define one

three shapes, all ordinary
function Row({ door, onRemove }) {
  // named, for anything longer than a line
  function handleRemove(event) {
    if (event.shiftKey) return onRemove(door.id, { silent: true });
    onRemove(door.id);
  }

  return (
    <li>
      {door.name}
      <button onClick={handleRemove}>Remove</button>
      <button onClick={() => onRemove(door.id)}>Quick</button>
      <button onClick={onRemove.bind(null, door.id)}>Also fine, rarer</button>
    </li>
  );
}

The event object

React passes a SyntheticEvent — a thin wrapper with the same API across browsers. Since React 17 it is no longer pooled, so you can keep a reference to it and read it later.

PropertyWhat it gives you
e.targetThe element the event started on
e.currentTargetThe element the handler is attached to — usually what you want
e.preventDefault()Stop the browser default: navigating, submitting, scrolling
e.stopPropagation()Stop it reaching handlers further up
e.key, e.shiftKey, e.metaKeyKeyboard and modifiers
e.nativeEventThe real DOM event underneath

preventDefault and stopPropagation

they do different things
// the browser would follow the link; we handle it ourselves
<a href="/doors" onClick={(e) => { e.preventDefault(); go("/doors"); }}>Doors</a>

// the row opens a detail panel, but the button inside must not open it
<li onClick={openDetail}>
  {door.name}
  <button onClick={(e) => { e.stopPropagation(); remove(door.id); }}>Remove</button>
</li>
Which one?

preventDefault cancels what the browser would do. stopPropagation cancels what your other handlers would do. Returning false from a React handler does neither — that was jQuery.

Where React really listens

React does not attach a listener to each element. It attaches one listener per event type to the root container and dispatches from there. This is why handlers are cheap even on a list of a thousand rows.

It also means a handler attached outside React with addEventListener may see an event before React does, and stopPropagation in a native listener on a parent will stop React seeing it at all.

Keyboard and focus

onFocus and onBlur in React bubble (the DOM ones do not), so you can put them on a wrapper to know when focus leaves a whole group.

the accessible defaults
<input onKeyDown={(e) => { if (e.key === "Enter") save(); }} />

// a div is not a button: it needs role, tabIndex, and keyboard handling
<div role="button" tabIndex={0}
  onClick={pick}
  onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); pick(); } }}>
  Pick
</div>

// or just use a button, and get all of that for free

Naming

  • The prop a component accepts: onSomething — onPick, onRemove, onChange.
  • The function that implements it: handleSomething — handlePick, handleRemove.
  • Name the prop after what happened, not what the parent will do with it: onPick, not onOpenDetailPanel. The child should not know.

Practice