
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.









