Before jumping into the problem statement,
let’s understand, what is
and how it is used in React.
- Mouse Events: onClick, onMouseEnter, onDrag etc.
- Form Events: onChange, onSubmit, etc.
- Keyboard Events: onKeyUp, onKeyDown etc.
- Window Events: onScroll etc.
When an event occurs on a page, the event flows from the document node to the target node and back to the document node.
There are four phases of the event.
- (0) None: No event is being executed.
- (1) Capture: Event handlers are executed down the tree (from document to target).
- (2) At target: Event handlers are executed at the target node.
- (3) Bubble: Event handlers are executed from target to parent.
Event Delegation in React 16 and earlier
React has been doing Event Delegation since the first release. When a DOM event fires on the document, respective event listeners get called.
In plain vanilla JS, an event listener is attached as below:
In React, we use the following code to add an event listener:
In React 16 or earlier, the event handlers are attached to the document node.
React figures out which component to call when a DOM event gets fired
Event bubbling for Event Delegation.
Event bubbles up through component to document where native event handlers have been attached by React.
How event delegation helps?
Binding event listeners to each node is not feasible and affects the performance. For better performance, the event listeners are attached to the parent node (refer - jsperf test ). In the case of React 16 or earlier versions, the event listeners are attached to the document node.
Problem with Event Delegation on the document node
Consider an application, which has jQuery or vanilla JS embedded in React or has different React versions. If an event handler at the document node calls
e.stopPropagation(), it causes a problem in React 16 and earlier versions. This behavior has resulted in some
Let’s consider the use-case of the Terms and Conditions Page. The user has to click on the Accept button to accept the terms and conditions. If the user clicked on the ‘Accept’ button, he should get the option of rejecting it.
To render above page, here is smaple code of App.jsx.
The above code will work as per requirement. If someone adds the following piece of code at the index or parent node of React, it will start breaking the functionality.
Because of the
e.stopPropagation(), the event handler written in React is not triggered.
Root cause of the problem
e.stopPropagation() stops bubbling up to parent nodes or stops capturing down to child nodes.
It is also important to understand the order of execution of the event handlers attached to a single node. In the majority of browsers, the event handlers execute in the order in which they are attached.
In this case, the
click event attached to the root will execute first.
It prevents the event from bubbling to the document node, due to which the terms button click handler is not called.
Event Delegation in React 17
In React 17, React no longer attaches the event listeners at the document level. Instead, it attaches them to the root DOM container into which our React tree is rendered.
So the difference is React 17 calls
rootNode.addEventListener() for most of the events, and the earlier versions, React calls
The above image is taken from React Official Document.
The event listener, which contains
added to the index page will not affect the click event of terms page.
Check out the pull request and React Official Document to learn more.