-
Notifications
You must be signed in to change notification settings - Fork 50.5k
Description
React version: 19.1.1
Chrome version: 138.0.7204.169
Steps To Reproduce
- Click "Open Outer Dialog"
- Click "Open Inner Dialog"
- Click "Close Inner Dialog" or press Esc on keyboard
Link to code example:
React repro: https://codesandbox.io/p/sandbox/qkndj4
Plain JS comparison: https://codepen.io/heguro/pen/XJmNeJr
The current behavior
In the React repro, clicking "Close Inner Dialog" produces the following logs:
inner dialog: onClose
outer dialog: onClose
div: onClose
In plain JS, the close and cancel events do not bubble, so the same operation only outputs inner dialog: onClose.
The expected behavior
Should only output the following log, same as plain JS behavior:
inner dialog: onClose
Additional context
In packages/react-dom-bindings/src/events/DOMPluginEventSystem.js, both 'close' and 'cancel' are listed in nonDelegatedEvents:
react/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
Lines 233 to 250 in 33a2bf7
| // We should not delegate these events to the container, but rather | |
| // set them on the actual target element itself. This is primarily | |
| // because these events do not consistently bubble in the DOM. | |
| export const nonDelegatedEvents: Set<DOMEventName> = new Set([ | |
| 'beforetoggle', | |
| 'cancel', | |
| 'close', | |
| 'invalid', | |
| 'load', | |
| 'scroll', | |
| 'scrollend', | |
| 'toggle', | |
| // In order to reduce bytes, we insert the above array of media events | |
| // into this Set. Note: the "error" event isn't an exclusive media event, | |
| // and can occur on other elements too. Rather than duplicate that event, | |
| // we just take it from the media events array. | |
| ...mediaEventTypes, | |
| ]); |
However, in packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js:
react/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js
Lines 199 to 209 in 33a2bf7
| // Some events don't bubble in the browser. | |
| // In the past, React has always bubbled them, but this can be surprising. | |
| // We're going to try aligning closer to the browser behavior by not bubbling | |
| // them in React either. We'll start by not bubbling onScroll, and then expand. | |
| const accumulateTargetOnly = | |
| !inCapturePhase && | |
| // TODO: ideally, we'd eventually add all events from | |
| // nonDelegatedEvents list in DOMPluginEventSystem. | |
| // Then we can remove this special list. | |
| // This is a breaking change that can wait until React 18. | |
| (domEventName === 'scroll' || domEventName === 'scrollend'); |
The TODO comment mentions plans to add all nonDelegatedEvents to prevent bubbling, originally planned for React 18, but we're now at React 19 and 'close' and 'cancel' events still bubble incorrectly. (Related PR: #19464 #19761)
Edit:
event.stopPropagation()can be used as a workaround to prevent bubbling.- Note that the
'cancel'event doesn't bubble on dialog but does on input elements in the DOM. See cancel event doesn't bubble w3c/webref#1212