5 min read
0%

Synthetic Event System

Back to Blog
Synthetic Event System

Synthetic Event System

React wraps native DOM events in synthetic events — cross-browser-normalized wrappers that provide a consistent API regardless of browser quirks.

What a Synthetic Event Is

function handleClick(e) {
  // e is a SyntheticEvent, not a native MouseEvent
  e.preventDefault();
  e.stopPropagation();
  console.log(e.target, e.currentTarget);
  console.log(e.nativeEvent); // the underlying native event
}

<button onClick={handleClick}>Click</button>

The SyntheticEvent interface mirrors the W3C spec, so preventDefault(), stopPropagation(), target, currentTarget all work consistently across browsers.

Event Delegation

React does not attach event listeners to individual DOM nodes. Instead, it attaches a single listener at the root container:

<div id="root">       ← one listener per event type
  <App>
    <button onClick={...}>   ← no listener attached here

When a native event bubbles up to the root, React reconstructs the component tree path and invokes matching handlers in order. This is more memory-efficient than per-element listeners and avoids listener leaks when components unmount.

React 17: Root Delegation Change

Before React 17, React delegated events to document. React 17 changed this to the root container element. This makes it safe to run multiple React versions on the same page (micro-frontends) without event conflicts.

Event Pooling (Removed in React 17)

React 16 and earlier reused synthetic event objects across handlers. Accessing event properties asynchronously would return null:

// React 16 bug:
function handleClick(e) {
  setTimeout(() => console.log(e.type), 0); // null — event was recycled
}

React 17 removed event pooling. Synthetic events are now regular objects. No more e.persist() needed.

Capture Phase

Add Capture suffix for capture-phase handlers:

<div onClickCapture={handleCapturePhase}>
  <button onClick={handleBubblePhase} />
</div>

Capture runs top-down, bubble runs bottom-up — same as native DOM.

Stopping Propagation

function Child({ onClick }) {
  return (
    <button
      onClick={e => {
        e.stopPropagation(); // stops React's synthetic bubble
        onClick();
      }}
    >
      Click
    </button>
  );
}

stopPropagation() on a synthetic event stops other React handlers but does not stop native listeners attached outside React. Use e.nativeEvent.stopImmediatePropagation() for that.

Passive Events

React attaches touchstart and wheel listeners as passive by default in React 17+. preventDefault() cannot be called on these events — it will warn in the console and have no effect.


Canvas is not supported in your browser