When to Avoid Synthetic Events in React

React’s SyntheticEvent wrapper is a powerful system that ensures cross-browser compatibility and performance optimization by pooling events and delegating them to the root element. However, there are specific development scenarios where relying on Synthetic Events can lead to unexpected bugs, performance bottlenecks, or integration issues. This article outlines the key situations where you should bypass React’s event system and opt for native DOM events instead.

1. Integrating with Third-Party Non-React Libraries

If your React application uses external vanilla JavaScript libraries—such as jQuery plugins, D3.js visualizations, or Leaflet maps—you should avoid Synthetic Events. These libraries operate outside of the React virtual DOM and rely directly on the native browser event model.

When you use React’s event system, events are delegated to the root element (React 17+) rather than the individual DOM node. This difference in event delegation can prevent native library event listeners from firing, or cause them to fire in the wrong order. To avoid this, use a React ref to target the DOM node directly and attach native event listeners using addEventListener.

2. Working with Web Components (Custom Elements)

Web Components use Custom Elements and the Shadow DOM, which frequently emit custom native events. React’s SyntheticEvent system does not automatically recognize or map custom events emitted by these components.

If your React component needs to listen to a custom event dispatched by a Web Component (e.g., my-element.dispatchEvent(new CustomEvent('custom-change'))), standard React JSX props like onCustomChange will not work. You must access the underlying Custom Element via a ref and attach a native event listener:

useEffect(() => {
  const element = customElementRef.current;
  element.addEventListener('custom-change', handleCustomChange);
  
  return () => {
    element.removeEventListener('custom-change', handleCustomChange);
  };
}, []);

3. High-Frequency, Performance-Critical Events

For high-frequency UI interactions—such as complex mouse tracking, canvas drawing, drag-and-drop operations, or real-time scrolling animations—the overhead of React’s SyntheticEvent creation and propagation can cause noticeable lag.

While React 17 removed event pooling (which previously caused issues with accessing event properties asynchronously), the synthetic wrapping layer still adds a minor processing cost. For buttery-smooth 60 FPS interactions, attaching native event listeners directly to the DOM nodes allows you to bypass React’s render phase entirely, especially when paired with CSS transitions or requestAnimationFrame.

4. Registering Global Window or Document Listeners

When you need to listen for events that occur outside your React component hierarchy—such as detecting a click outside a modal to close it, or listening for global keyboard shortcuts—Synthetic Events are not suitable.

React event handlers only capture events that bubble up through React’s internal component tree. For global actions, you must register native event listeners on window or document inside a useEffect hook.

useEffect(() => {
  const handleKeyDown = (event) => {
    if (event.key === 'Escape') {
      closeModal();
    }
  };

  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, []);

5. Accessing Cutting-Edge or Non-Standard Event Properties

React’s SyntheticEvent is a synthetic wrapper designed to normalize event properties across Safari, Chrome, Firefox, and Edge. Because of this normalization, React only includes properties that are widely supported and standardized.

If you need to access highly specific, non-standard, or cutting-edge experimental properties of a browser event, they may not be exposed on the React Synthetic Event object. While you can often access these via event.nativeEvent, in complex event propagation scenarios, it is safer to attach a native event listener to ensure accurate property readouts directly from the browser’s engine.