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.