diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js index 034b2d7290a..d43bcd1a723 100644 --- a/packages/react-art/src/ReactARTHostConfig.js +++ b/packages/react-art/src/ReactARTHostConfig.js @@ -363,6 +363,8 @@ export function appendChildToContainer(parentInstance, child) { child.inject(parentInstance); } +export const appendChildToPortalContainer = appendChildToContainer; + export function insertBefore(parentInstance, child, beforeChild) { invariant( child !== beforeChild, diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js index e695a845ff5..0fdb06641a5 100644 --- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js @@ -2677,6 +2677,23 @@ describe('ReactDOMComponent', () => { const container = document.createElement('div'); const portalContainer = document.createElement('div'); + function Component() { + return ReactDOM.createPortal( +
{}} />, + portalContainer, + ); + } + const root = ReactDOM.unstable_createRoot(container); + root.render(); + jest.runAllTimers(); + + expect(typeof portalContainer.onclick).toBe('function'); + }); + + it('adds onclick handler to a portal root mounted via a legacy React root', () => { + const container = document.createElement('div'); + const portalContainer = document.createElement('div'); + function Component() { return ReactDOM.createPortal(
{}} />, @@ -2688,7 +2705,21 @@ describe('ReactDOMComponent', () => { expect(typeof portalContainer.onclick).toBe('function'); }); - it('does not add onclick handler to the React root', () => { + it('does not add onclick handler to a React root', () => { + const container = document.createElement('div'); + + function Component() { + return
{}} />; + } + + const root = ReactDOM.unstable_createRoot(container); + root.render(); + jest.runAllTimers(); + + expect(typeof container.onclick).not.toBe('function'); + }); + + it('does not add onclick handler to a legacy React root', () => { const container = document.createElement('div'); function Component() { diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index 86b88ce2bfa..fd8114acaa6 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -370,19 +370,29 @@ export function appendChildToContainer( parentNode = container; parentNode.appendChild(child); } - // This container might be used for a portal. +} + +export function appendChildToPortalContainer( + container: DOMContainer, + child: Instance | TextInstance, +): void { + let parentNode; + if (container.nodeType === COMMENT_NODE) { + parentNode = (container.parentNode: any); + parentNode.insertBefore(child, container); + } else { + parentNode = container; + parentNode.appendChild(child); + } + // If something inside a portal is clicked, that click should bubble // through the React tree. However, on Mobile Safari the click would // never bubble through the *DOM* tree unless an ancestor with onclick // event exists. So we wouldn't see it and dispatch it. - // This is why we ensure that non React root containers have inline onclick + // This is why we ensure that portal containers have inline onclick // defined. // https://github.com/facebook/react/issues/11918 - const reactRootContainer = container._reactRootContainer; - if ( - (reactRootContainer === null || reactRootContainer === undefined) && - parentNode.onclick === null - ) { + if (parentNode.onclick === null) { // TODO: This cast may not be sound for SVG, MathML or custom elements. trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement)); } diff --git a/packages/react-native-renderer/src/ReactNativeHostConfig.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js index 785e98c1493..4bffd4a3e21 100644 --- a/packages/react-native-renderer/src/ReactNativeHostConfig.js +++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js @@ -309,6 +309,8 @@ export function appendChildToContainer( ); } +export const appendChildToPortalContainer = appendChildToContainer; + export function commitTextUpdate( textInstance: TextInstance, oldText: string, diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 42726f41303..e1dfd1ca966 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -391,6 +391,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { appendChild, appendChildToContainer, + appendChildToPortalContainer: appendChildToContainer, insertBefore, insertInContainerBefore, removeChild, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 76e136dd7db..8d973bad049 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -75,6 +75,7 @@ import { commitTextUpdate, appendChild, appendChildToContainer, + appendChildToPortalContainer, insertBefore, insertInContainerBefore, removeChild, @@ -951,7 +952,11 @@ function commitPlacement(finishedWork: Fiber): void { } } else { if (isContainer) { - appendChildToContainer(parent, node.stateNode); + if (parentFiber.tag === HostRoot) { + appendChildToContainer(parent, node.stateNode); + } else { + appendChildToPortalContainer(parent, node.stateNode); + } } else { appendChild(parent, node.stateNode); } diff --git a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js index 4dbc13bd814..de825a29d38 100644 --- a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js +++ b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js @@ -49,6 +49,9 @@ describe('ReactFiberHostContext', () => { appendChildToContainer: function() { return null; }, + appendChildToPortalContainer: function() { + return null; + }, supportsMutation: true, }); @@ -97,6 +100,9 @@ describe('ReactFiberHostContext', () => { appendChildToContainer: function() { return null; }, + appendChildToPortalContainer: function() { + return null; + }, supportsMutation: true, }); diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js index b217c22f1c6..212a054a8d7 100644 --- a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js @@ -68,6 +68,8 @@ export const supportsHydration = $$$hostConfig.supportsHydration; // ------------------- export const appendChild = $$$hostConfig.appendChild; export const appendChildToContainer = $$$hostConfig.appendChildToContainer; +export const appendChildToPortalContainer = + $$$hostConfig.appendChildToPortalContainer; export const commitTextUpdate = $$$hostConfig.commitTextUpdate; export const commitMount = $$$hostConfig.commitMount; export const commitUpdate = $$$hostConfig.commitUpdate; diff --git a/packages/react-test-renderer/src/ReactTestHostConfig.js b/packages/react-test-renderer/src/ReactTestHostConfig.js index b4e6f681fac..58ab914f7b2 100644 --- a/packages/react-test-renderer/src/ReactTestHostConfig.js +++ b/packages/react-test-renderer/src/ReactTestHostConfig.js @@ -251,6 +251,7 @@ export function resetTextContent(testElement: Instance): void { } export const appendChildToContainer = appendChild; +export const appendChildToPortalContainer = appendChild; export const insertInContainerBefore = insertBefore; export const removeChildFromContainer = removeChild; diff --git a/packages/shared/HostConfigWithNoMutation.js b/packages/shared/HostConfigWithNoMutation.js index badb80920b1..87b86911b2c 100644 --- a/packages/shared/HostConfigWithNoMutation.js +++ b/packages/shared/HostConfigWithNoMutation.js @@ -25,6 +25,7 @@ function shim(...args: any) { export const supportsMutation = false; export const appendChild = shim; export const appendChildToContainer = shim; +export const appendChildToPortalContainer = shim; export const commitTextUpdate = shim; export const commitMount = shim; export const commitUpdate = shim;