diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js index 95809b7a1b5b4..e695a845ff505 100644 --- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js @@ -2659,4 +2659,44 @@ describe('ReactDOMComponent', () => { document.body.removeChild(container); } }); + + describe('iOS Tap Highlight', () => { + it('adds onclick handler to elements with onClick prop', () => { + const container = document.createElement('div'); + + const elementRef = React.createRef(); + function Component() { + return
{}} />; + } + + ReactDOM.render(, container); + expect(typeof elementRef.current.onclick).toBe('function'); + }); + + it('adds onclick handler to a portal root', () => { + const container = document.createElement('div'); + const portalContainer = document.createElement('div'); + + function Component() { + return ReactDOM.createPortal( +
{}} />, + portalContainer, + ); + } + + ReactDOM.render(, container); + expect(typeof portalContainer.onclick).toBe('function'); + }); + + it('does not add onclick handler to the React root', () => { + const container = document.createElement('div'); + + function Component() { + return
{}} />; + } + + ReactDOM.render(, container); + expect(typeof container.onclick).not.toBe('function'); + }); + }); }); diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index f0cf8ea162795..83563ee14f9d5 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -126,7 +126,7 @@ if (__DEV__) { ReactControlledComponent.setRestoreImplementation(restoreControlledState); -type DOMContainer = +export type DOMContainer = | (Element & { _reactRootContainer: ?Root, }) diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index a9dd23f2fac3c..c5dc1f34d1267 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -36,6 +36,8 @@ import { DOCUMENT_FRAGMENT_NODE, } from '../shared/HTMLNodeType'; +import type {DOMContainer} from './ReactDOM'; + export type Type = string; export type Props = { autoFocus?: boolean, @@ -342,7 +344,7 @@ export function appendChild( } export function appendChildToContainer( - container: Container, + container: DOMContainer, child: Instance | TextInstance, ): void { let parentNode; @@ -358,9 +360,14 @@ export function appendChildToContainer( // 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 containers have inline onclick defined. + // This is why we ensure that non React root containers have inline onclick + // defined. // https://github.com/facebook/react/issues/11918 - if (parentNode.onclick === null) { + const reactRootContainer = container._reactRootContainer; + if ( + (reactRootContainer === null || reactRootContainer === undefined) && + parentNode.onclick === null + ) { // TODO: This cast may not be sound for SVG, MathML or custom elements. trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement)); }