React 16 Portal Events Issue (Hack)
Demonstrates circumstances in which mobile events are not bubbling from React portal content. This version resolves the issue with a hack.
by Zebulon Young
HTML
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="modal-root"></div>
<div id="container"></div>
CSS
div {
box-sizing: border-box;
}
.block {
height: 100px;
border: 1px solid #CCC;
border-radius: 4px;
padding: 10px;
margin: 5px 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.backdropX {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.4);
}
.content {
position: absolute;
bottom: 10px;
left: 10px;
right: 10px;
padding: 10px;
border-radius: 4px;
background-color: #FFF;
}
Babel + JSX
const modalRoot = document.getElementById( 'modal-root' );
class Modal extends React.Component {
constructor(props: ModalProps) {
super(props);
this.el = document.createElement( 'div' );
// Addition of a no-op click handler here causes events to be propagated
// up the React component tree, as expected.
this.el.onclick = () => null;
}
componentDidMount() {
modalRoot && modalRoot.appendChild( this.el );
}
componentWillUnmount() {
modalRoot && modalRoot.removeChild( this.el );
}
render() {
const { render } = this.props;
return ReactDOM.createPortal((
<div className="backdrop">
<div className="content">
{ render() }
</div>
</div>
), this.el );
}
}
class App extends React.Component {
state = {
toggle: false
};
render() {
const { toggled } = this.state;
return (
<div
onClick={ () => this.toggle() }>
<div className="block">
Main: { toggled ? 'On' : 'Off' }
</div>
<Modal render={ () => (
<div className="block">
{ 'Portal' }
</div>
)} />
</div>
);
}
toggle() {
this.setState( state => ({
toggled: !state.toggled
}));
}
}
ReactDOM.render(
<App />,
document.getElementById( 'container' )
);