- React
- React Internals: Fiber Architecture and Reconciliation
- SyntheticEvent System & Event Delegation
SyntheticEvent System & Event Delegation
React SyntheticEvents wrap native browser events to provide cross-browser consistency. In React 17+, events are delegated to the root DOM container (#root) rather than document, and event pooling was completely removed, allowing events to be read asynchronously.
Key Learning Objectives
Understand event delegation and why React attaches listeners to the root container (#root) in React 17+.
Explain how React 16 attached to document and why that caused nested micro-frontend bugs.
Explain the removal of SyntheticEvent pooling in React 17 (e.persist() is no longer required).
Predict event execution order between native DOM document listeners and React SyntheticEvents.
The Interview Problem & Context
What is logged to the console when tracing the execution order of a button click with a React handler, a React root container handler, and a native document listener in React 17+?
1// Simulating event bubbling in React 17+2function simulateEventBubbling() {3 const logs: string[] = [];45 // In React 17+, all React events are delegated to #root!6 // When a button inside #root is clicked:7 // 1. Native click bubbles up to #root container8 // 2. React dispatches SyntheticEvents down & up component tree9 logs.push('target-react');10 logs.push('root-react');1112 // 3. Native event bubbles past #root to document!13 logs.push('doc-native');1415 return logs.join(' ');16}1718console.log(simulateEventBubbling());
Predict Console & Render Output
target-react root-react doc-native
doc-native target-react root-react
target-react doc-native root-react
root-react target-react doc-native
Execution Trace
Step 1 of 4 (Line 8)React SyntheticEvent executes on target component.
Deep Architectural Explanation
The Evolution of the SyntheticEvent System
1. Event Delegation: React 16 vs React 17+
- React 16: React attached a single event listener for each event type to
document(document.addEventListener('click', ...)).- Problem: If you embedded a React app inside a legacy jQuery app or nested two React versions (micro-frontends), an
e.stopPropagation()in the child React app failed to stop events on the outer app.
- Problem: If you embedded a React app inside a legacy jQuery app or nested two React versions (micro-frontends), an
- React 17+: React attaches event listeners to the root DOM container where
createRoot(rootNode)is called:
const rootNode = document.getElementById('root');
// React attaches listeners to rootNode, NOT document!This makes embedding multiple independent React versions on the same page safe and seamless.
2. Event Pooling Removal (React 17+)
In React 16, SyntheticEvent objects were pooled and wiped clean after the handler executed to save memory. Accessing e.target inside an asynchronous callback (setTimeout) threw an error unless you called e.persist().
- In React 17+, event pooling was completely removed. You can safely read event properties in async callbacks without calling
e.persist().
Common Interview Traps & Pitfalls
Calling e.persist() in React 17 or 18 (it is now a no-op).
Assuming React attaches listeners directly to the individual button DOM nodes.
Expecting e.stopPropagation() in React to stop native listeners attached to the window or document.
Senior Interviewer Follow-Up Probes
How does event.nativeEvent differ from the SyntheticEvent object?
How do you attach a native event listener to window in React without breaking unmount cleanups?
Why does stopPropagation on a SyntheticEvent not stop native listeners attached to the target element itself?
Frequently Asked Interview Questions
Where does React attach event listeners in React 17 and 18?
To the root DOM container (e.g., #root) where your React application is mounted, rather than the global document node.
What was event pooling and why was it removed in React 17?
React reused SyntheticEvent objects across events to save memory, clearing properties after the handler ran. It was removed because modern JS engines handle object creation fast enough, and pooling caused confusing bugs in async callbacks.
Can you read event.target inside a setTimeout in React 18?
Yes. Because event pooling was removed, the SyntheticEvent retains its properties in asynchronous callbacks without needing e.persist().
How does event delegation improve React performance?
Instead of attaching hundreds of separate event listeners to individual table rows or list items, React attaches one listener to the root container, saving memory and speeding up mounting.
