React Curriculum

SyntheticEvent System & Event Delegation

Medium100% Free~25 mins#react#syntheticevents#event-delegation#react17#root-container#bubbling
Interview Answer (30-Second Summary)

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[] = [];
4
5 // 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 container
8 // 2. React dispatches SyntheticEvents down & up component tree
9 logs.push('target-react');
10 logs.push('root-react');
11
12 // 3. Native event bubbles past #root to document!
13 logs.push('doc-native');
14
15 return logs.join(' ');
16}
17
18console.log(simulateEventBubbling());
Predict Console & Render Output
A

target-react root-react doc-native

B

doc-native target-react root-react

C

target-react doc-native root-react

D

root-react target-react doc-native

Execution Trace
Step 1 of 4 (Line 8)

React SyntheticEvent executes on target component.

Component / Call Stack
simulateEventBubbling()
Hook State & Scope
logs[0]:"target-react"
Console Output(No console logs emitted)
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.
  • React 17+: React attaches event listeners to the root DOM container where createRoot(rootNode) is called:
js
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
What is a SyntheticEvent in React?

A cross-browser wrapper around the native browser event that standardizes event properties and behaviors across Chrome, Safari, Firefox, and Edge.

To the root DOM container (e.g., #root) where your React application is mounted, rather than the global document node.

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.

Yes. Because event pooling was removed, the SyntheticEvent retains its properties in asynchronous callbacks without needing e.persist().

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.