Repository: facebook/react
Pull request: Revert "Revert #19348"

Reverts facebook/react#19354

Changed files:
filename: packages/react-dom/src/client/ReactDOMEventHandle.js
patch: @@ -22,7 +22,7 @@ import {
   getEventListenerMap,
   getFiberFromScopeInstance,
 } from './ReactDOMComponentTree';
-import {ELEMENT_NODE} from '../shared/HTMLNodeType';
+import {ELEMENT_NODE, COMMENT_NODE} from '../shared/HTMLNodeType';
 import {
   listenToNativeEvent,
   addEventTypeToDispatchConfig,
@@ -90,14 +90,17 @@ function registerEventOnNearestTargetContainer(
 ): void {
   // If it is, find the nearest root or portal and make it
   // our event handle target container.
-  const targetContainer = getNearestRootOrPortalContainer(targetFiber);
+  let targetContainer = getNearestRootOrPortalContainer(targetFiber);
   if (targetContainer === null) {
     invariant(
       false,
       'ReactDOM.createEventHandle: setListener called on an target ' +
         'that did not have a corresponding root. This is likely a bug in React.',
     );
   }
+  if (targetContainer.nodeType === COMMENT_NODE) {
+    targetContainer = ((targetContainer.parentNode: any): Element);
+  }
   const listenerMap = getEventListenerMap(targetContainer);
   listenToNativeEvent(
     topLevelType,

filename: packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js
patch: @@ -3009,6 +3009,82 @@ describe('DOMModernPluginEventSystem', () => {
 
               expect(onClick).toHaveBeenCalledTimes(1);
             });
+
+            // @gate experimental
+            it('handle propagation of click events between disjointed comment roots', () => {
+              const buttonRef = React.createRef();
+              const divRef = React.createRef();
+              const log = [];
+              const setClick = ReactDOM.unstable_createEventHandle('click');
+              const setClickCapture = ReactDOM.unstable_createEventHandle(
+                'click',
+                {capture: true},
+              );
+              const onClick = jest.fn(e =>
+                log.push(['bubble', e.currentTarget]),
+              );
+              const onClickCapture = jest.fn(e =>
+                log.push(['capture', e.currentTarget]),
+              );
+
+              function Child() {
+                React.useEffect(() => {
+                  const click1 = setClick(divRef.current, onClick);
+                  const click2 = setClickCapture(
+                    divRef.current,
+                    onClickCapture,
+                  );
+                  return () => {
+                    click1();
+                    click2();
+                  };
+                });
+
+                return <div ref={divRef}>Click me!</div>;
+              }
+
+              function Parent() {
+                React.useEffect(() => {
+                  const click1 = setClick(buttonRef.current, onClick);
+                  const click2 = setClickCapture(
+                    buttonRef.current,
+                    onClickCapture,
+                  );
+                  return () => {
+                    click1();
+                    click2();
+                  };
+                });
+
+                return <button ref={buttonRef} />;
+              }
+
+              // We use a comment node here, then mount to it
+              const disjointedNode = document.createComment(
+                ' react-mount-point-unstable ',
+              );
+              ReactDOM.render(<Parent />, container);
+              Scheduler.unstable_flushAll();
+              buttonRef.current.appendChild(disjointedNode);
+              ReactDOM.render(<Child />, disjointedNode);
+              Scheduler.unstable_flushAll();
+
+              const buttonElement = buttonRef.current;
+              dispatchClickEvent(buttonElement);
+              expect(onClick).toHaveBeenCalledTimes(1);
+              expect(onClickCapture).toHaveBeenCalledTimes(1);
+              expect(log[0]).toEqual(['capture', buttonElement]);
+              expect(log[1]).toEqual(['bubble', buttonElement]);
+
+              const divElement = divRef.current;
+              dispatchClickEvent(divElement);
+              expect(onClick).toHaveBeenCalledTimes(3);
+              expect(onClickCapture).toHaveBeenCalledTimes(3);
+              expect(log[2]).toEqual(['capture', buttonElement]);
+              expect(log[3]).toEqual(['capture', divElement]);
+              expect(log[4]).toEqual(['bubble', divElement]);
+              expect(log[5]).toEqual(['bubble', buttonElement]);
+            });
           });
         });
       },