milan
milan

Reputation: 2417

modal is closed when clicking inside modal content

I am trying to create a customizable generic scalable modal using reactjs. I know there is tons of library but I am just developing for understanding the react ecosystem. However, in my modal, when i clicked on inside the modal content, the modal gets closed. The modal should close only when clicking on the close button and outside the modal area.

Here is what I have done. The demo is there too

https://codepen.io/anon/pen/mzMdoq

Code

class Portal extends React.Component {
  render() {
    return ReactDOM.createPortal(
      <div
        style={{
          position: "absolute",
          top: "0",
          bottom: "0",
          left: "0",
          right: "0",
          display: "grid",
          justifyContent: "center",
          alignItems: "center",
          backgroundColor: "rgba(0,0,0,0.3)"
        }}
        onClick={this.props.onClose}
      >
        <div
          style={{
            padding: 20,
            background: "#fff",
            borderRadius: "2px",
            display: "inline-block",
            minHeight: "300px",
            margin: "1rem",
            position: "relative",
            minWidth: "300px",
            boxShadow: "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)",
            justifySelf: "center"
          }}
        >
          {this.props.children}
          <hr />
          <button onClick={this.props.onClose}>Close</button>
        </div>
      </div>,
      document.body
    );
  }
}

class Modal extends React.Component {
  modalRef = React.createRef();
  componentDidMount() {
    document.addEventListener("mousedown", this.handleClick, false);
  }
  componentWillUnmount() {
    document.removeEventListener("mousedown", this.handleClick, false);
  }
  handleClick = e => {
    const node = this.modalRef.current;
    console.log("props", node);
    if (
      this.modalRef &&
      this.modalRef.current &&
      this.modalRef.current.contains(e.target)
    ) {
      return;
    } else {
      this.props.onClose();
    }
  };
  render() {
    return (
      <React.Fragment>
        <Portal {...this.props} ref={this.modalRef} />
      </React.Fragment>
    );
  }
}

class ModalExample extends React.Component {
  state = { showModal: false };
  showModal = () => this.setState({ showModal: true });
  handleClose = () => this.setState({ showModal: false });
  render() {
    const { showModal } = this.state;
    return (
      <React.Fragment>
        <button onClick={this.showModal}>Modal</button>
        {showModal ? (
          <Modal showModal={showModal} onClose={this.handleClose}>
            This is the modal content!
          </Modal>
        ) : null}
      </React.Fragment>
    );
  }
}

function App() {
  return (
    <div className="App">
      <h1>Hello</h1>
      <ModalExample />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

Upvotes: 1

Views: 2922

Answers (4)

blaz
blaz

Reputation: 4068

Your overlay div wraps the modal content div, so clicking on content is also considered clicking on the overlay.

One way to solve this is to not nest modal content inside overlay, but move it outside:

return ReactDOM.createPortal(
      <div style={{
          position: "absolute",
          top: "0",
          bottom: "0",
          left: "0",
          right: "0",
          display: "grid",
          justifyContent: "center",
          alignItems: "center"
        }}>
      <div
        style={{
          position: "absolute",
          top: "0",
          bottom: "0",
          left: "0",
          right: "0",
          display: "grid",
          justifyContent: "center",
          alignItems: "center",
          backgroundColor: "rgba(0,0,0,0.3)"
        }} onClick={this.props.onClose}
      />
        <div
          style={{
            padding: 20,
            background: "#fff",
            borderRadius: "2px",
            display: "inline-block",
            minHeight: "300px",
            margin: "1rem",
            position: "relative",
            minWidth: "300px",
            boxShadow: "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)",
            justifySelf: "center"
          }}
        >
          {this.props.children}
          <hr />
          <button onClick={this.props.onClose}>Close</button>
        </div>
      </div>,
      document.body
    );

Upvotes: 0

akshay kishore
akshay kishore

Reputation: 1027

Hmm this is not exactly an answer but still if it helps...

I usually generate modals this way

https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_modal

Here as you can see the modal already exists in the DOM and is hidden or displayed as required using js. It's a bit different to yours as the div is pre-existing.

Upvotes: 0

Placido
Placido

Reputation: 1385

Add e.stopPropagation() to onClick of your modal's body. Something like this

<div
  style={{
    padding: 20,
    background: "#fff",
    borderRadius: "2px",
    display: "inline-block",
    minHeight: "300px",
    margin: "1rem",
    position: "relative",
    minWidth: "300px",
    boxShadow: "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)",
    justifySelf: "center"
  }}
  onClick={e => e.stopPropagation()}
>
  {this.props.children}
  <hr />
  <button onClick={this.props.onClose}>Close</button>
</div>

Upvotes: 3

Lazar Nikolic
Lazar Nikolic

Reputation: 4394

You should remove onClick={this.props.onClose} inside ReactDOM.createPortal and it will work fine

Upvotes: 0

Related Questions