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' )
);