Understanding React's Synthetic Event System and Event Handling Mechanism
React’s synthetic event system wraps native browser events into reusable SyntheticEvent objects, uses a delegated single listener per event type, maps event names through registration modules and plugins, pools event instances, and simulates capture and bubble phases across the fiber tree, with key changes in React 17.
React provides a synthetic event system that maps native browser events to React‑specific events such as onClick and onMouseLeave , allowing a consistent interface across browsers.
The term “synthetic event” refers to an object like SyntheticEvent (or its subclasses) that wraps the native event. For example, a native click event becomes an onClick synthetic event, while a pair of native mouseout and mouseover events are combined to produce onMouseLeave . This technique is also used in libraries like fastclick to synthesize click from touch events.
React’s event handling is divided into two stages: event binding (when the component mounts or updates) and event triggering (when a native event fires).
During binding, React consults three core data structures:
registrationNameModule – maps React event names (e.g., onClick ) to the plugin that handles them.
registrationNameDependencies – maps each React event to the native event types it depends on (e.g., onMouseLeave depends on mouseout and mouseover ).
plugins – the array of loaded event plugins (e.g., LegacySimpleEventPlugin , LegacyEnterLeaveEventPlugin ).
The injection of plugins occurs in ReactDOMClientInjection via:
injectEventPluginsByName({ SimpleEventPlugin: LegacySimpleEventPlugin, EnterLeaveEventPlugin: LegacyEnterLeaveEventPlugin, ChangeEventPlugin: LegacyChangeEventPlugin, SelectEventPlugin: LegacySelectEventPlugin, BeforeInputEventPlugin: LegacyBeforeInputEventPlugin });
An example of registrationNameModule looks like:
{ onBlur: SimpleEventPlugin, onClick: SimpleEventPlugin, onClickCapture: SimpleEventPlugin, onChange: ChangeEventPlugin, onChangeCapture: ChangeEventPlugin, onMouseEnter: EnterLeaveEventPlugin, onMouseLeave: EnterLeaveEventPlugin, ... }
The corresponding registrationNameDependencies is:
{ onBlur: ['blur'], onClick: ['click'], onClickCapture: ['click'], onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'], onMouseEnter: ['mouseout', 'mouseover'], onMouseLeave: ['mouseout', 'mouseover'], ... }
Each plugin is an object with eventTypes (metadata about the synthetic events it handles) and an extractEvents function that creates and returns a synthetic event when its native event fires.
The binding workflow proceeds as follows:
React’s diff phase marks DOM nodes that need creation or update.
For each prop, registrationNameModule is consulted; if the prop is an event name, processing continues.
registrationNameDependencies tells React which native event types the synthetic event depends on.
If those native events have not yet been registered, React attaches a single listener to the document (or, in React 17, the container node) whose callback is dispatchEvent .
Because the listener is attached only once per native event type, multiple onClick props share the same underlying listener.
This delegation model means React does not store business‑logic listeners on DOM elements; it merely guarantees that the native event bubbles up to the delegated listener, where React dispatches the appropriate synthetic event to the relevant component instances.
When a native event occurs, React calls dispatchEventForLegacyPluginEventSystem :
export function dispatchEventForLegacyPluginEventSystem(topLevelType, eventSystemFlags, nativeEvent, targetInst) { const bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst, eventSystemFlags); try { batchedEventUpdates(handleTopLevel, bookKeeping); } finally { releaseTopLevelCallbackBookKeeping(bookKeeping); } }
batchedEventUpdates enables React’s batching mode, then handleTopLevel iterates over all plugins, invoking each plugin’s extractEvents for the relevant synthetic event type.
Inside a plugin such as LegacySimpleEventPlugin :
The native event type determines which synthetic event class to instantiate (e.g., SyntheticMouseEvent for click ).
If an instance of that class exists in the object pool, it is reused; otherwise a new instance is created.
The synthetic event’s properties are populated from the native event and then dispatched along the component tree.
Event propagation in React simulates the DOM capture and bubble phases:
The tree of React fiber instances from the target node up to the root is traversed.
First, the traversal goes upward (capture phase) invoking props like onClickCapture .
Then, the traversal goes downward (bubble phase) invoking props like onClick .
NetEase Cloud Music Tech Team
Official account of NetEase Cloud Music Tech Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.