Published on

Event Handling in React 18

Introduction

In React, event handling differs from traditional DOM event listening. Events are encapsulated within synthetic events that provide an interface consistent with native events, but they normalize behaviors across different browsers. Instead of attaching event handlers to individual HTML elements, React employs a technique known as event delegation, attaching a single event listener to the root element.

Event Delegation

React utilizes a technique known as event delegation to handle events efficiently. This approach involves attaching a single event listener to the root HTML element, rather than multiple listeners spread across various components. This centralized event management system significantly reduces the overhead associated with attaching and detaching event listeners directly to DOM elements, especially in large applications.

Here's an example to illustrate:

export function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount((prevCount) => prevCount + 1);
  }

  return (
    <>
      <h1>Count: {count}</h1>
      <button onClick={handleClick}>Increment</button>
    </>
  );
}
When React creates the root container using ReactDOM.createRoot(), it attaches event listeners to the root HTML element for all supported events and both event capture and bubble phases. root event listener root event listener
The call graph of listener registration illustrates as below: event registration call graph

Synthetic Events

React abstracts the browser's native events into synthetic events, normalizing them for consistent behavior across different browsers. Unlike native event handlers, the event handler specified in a React component is not directly attached to the HTML element. Instead, an empty function (noop()) is attached.
Consequently, calling e.nativeEvent.stopPropagation() within an event handler will NOT prevent event bubbling, nor calling e.preventDefault() or returning false.
And calling e.stopPropagation() will stop only the propagation of synthetic events within the React event system. synthetic event

Event Dispatch

When the button is clicked in the example above, the "click" event is captured by the root HTML element, which then executes the dispatchDiscreteEvent() function. The native event is passed to dispatchDiscreteEvent() as an argument, including a reference to the clicked HTML button element. React attaches a fiber node reference of the host component to the corresponding HTML element as __reactFiber$ + randomKey. The host component refers to components that correspond to actual DOM elements like div, span, button, etc.

react-fiber-random-key

In detail, dispatchDiscreteEvent() calls dispatchEventsForPlugins(), which performs the following steps:

  1. Invokes getEventTarget() to retrieve the original clicked HTML element.
  2. Creates a new dispatchQueue as there might be multiple registered listeners.
  3. Invokes extractEvents() to create a new SyntheticEvent and pushes it to the dispatchQueue.
  4. Invokes processDispatchQueue() to execute the specified event listener.
dispatch-event-for-plugins synthetic-event-object call-callback
The call graph illustrates as below: event dispatch call graph

Event Priority

ReactDOM.createRoot() method not only creates the root container but also registers three types of event listeners on the root element, each processed at a different priority.
The three types of event listeners are:

  1. dispatchDiscreteEvent(): Handles high-priority events such as mouse clicks, keyboard presses, and touch inputs, ensuring a responsive user interface.
  2. dispatchContinuousEvent(): Manages events like mouse movement, scrolling, and dragging, which occur frequently and do not require immediate processing.
  3. dispatchEvent(): Handles normal priority events, such as "canplay," "audio," and "error."

You may refer to the event priority definitions in the React source code: ReactDOMEventListener.js https://github.com/facebook/react/blob/v18.3.1/packages/react-dom/src/events/ReactDOMEventListener.js#L411

When an event listener includes a state change, a new re-render needs to be scheduled. React calculates the update lane based on the event's priority, then passes the update lane to enqueueConcurrentHookUpdate() to mark the fiber for updating, followed by a call to scheduleUpdateOnFiber() to schedule the update.

dispatch-set-update request update lane
The call graph illustrates as below: re-render

Conclusion

By leveraging event delegation, synthetic events, and a sophisticated event handling process, React efficiently manages events, prioritizing critical updates and maintaining a responsive interface.

Aside: What is Event Phases

Events propagate along a path from the root to the target node. This process is divided into three phases:

  1. Capture Phase: The event is dispatched from the root node to the direct parent of the target node. You can use the third argument of addEventListener to control whether to trigger the event during the capture phase.
  2. Target Phase: The event is dispatched to the target node.
  3. Bubbling Phase: The event propagates from the target's direct parent node back up to the root. Most events bubble, with the notable exception of the focus event, which does not.

When registering an event listener, you can use the options parameter to control the behavior of the event:

  • Boolean Value: If true, the event will be triggered during the capture phase; if false, during the bubbling phase.
  • Object:
{
  capture: true, // Same as boolean value
  once: true, // The event listener should be invoked at most once and removed after it is triggered
  passive: true // The event listener will not cancel the event; this improves responsiveness, especially on mobile devices
}

If you set passive to true, calling e.preventDefault() within that event listener will be ignored, and the default action will not be prevented. This allows the browser to proceed with the default action immediately, which can enhance the responsiveness of certain actions, particularly on mobile devices where touch and scroll events are frequent.