Hacky way to add Modals to web

Until there’s official support for Modal from react-native-web, here’s how I just hacked up modal support in my app:

Made a Modal.js file that I import from:

import { Platform } from "react-native";

let Modal;

if (Platform.OS !== 'web') {
  Modal = require('react-native').Modal;
} else {
  Modal = require('./WebModal').default;
}

export default Modal;

And then WebModal.js:

import React, { Component } from 'react';
import ReactDOM from "react-dom";

// create and get reference to Modal DOM node
const appRoot = document.getElementById('root');
appRoot.insertAdjacentHTML('afterend', '<div id="modal-root"></div>');
const modalRoot = document.getElementById('modal-root');

class InnerModal extends Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {
    // https://reactjs.org/docs/portals.html

    // The portal element is inserted in the DOM tree after
    // the Modal's children are mounted, meaning that children
    // will be mounted on a detached DOM node. If a child
    // component requires to be attached to the DOM tree
    // immediately when mounted, for example to measure a
    // DOM node, or uses 'autoFocus' in a descendant, add
    // state to Modal and only render the children when Modal
    // is inserted in the DOM tree.
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }

  render() {
    const containerStyle = {
      // copied from RNW View/StyleSheet/constants
      alignItems: 'stretch',
      border: '0 solid black',
      boxSizing: 'border-box',
      display: 'flex',
      flexBasis: 'auto',
      flexDirection: 'column',
      flexShrink: 0,
      marginTop: 0,
      marginRight: 0,
      marginBottom: 0,
      marginLeft: 0,
      minHeight: 0,
      minWidth: 0,
      paddingTop: 0,
      paddingRight: 0,
      paddingBottom: 0,
      paddingLeft: 0,
      position: 'relative',
      zIndex: 0,

      // modal
      position: 'absolute',
      left: 0,
      top: 0,
      right: 0,
      bottom: 0,

      // etc
      backgroundColor: this.props.transparent ? 'transparent' : 'white',
    };

    return ReactDOM.createPortal(
      <div style={containerStyle}>
        {this.props.children}
      </div>,
      this.el,
    );
  }
}

export default class Modal extends Component {
  render() {
    if (this.props.visible) {
      return <InnerModal {...this.props}/>;
    }
    return null;
  }
}

EDIT:
You can also remove appRoot.insertAdjacentHTML('afterend', '<div id="modal-root"></div>'); and instead add that div to index.html directly after running expo customize:web

This works just fine :stuck_out_tongue_winking_eye: (only supports web though)

1 Like