Create a Modal in React

which closes when clicking outside of it

·

3 min read

Implement a Basic Modal

You can view the complete code here codesandbox.io/s/naughty-platform-ftyk3s

First, add a div to hold the modal in public/index.html

// public/index.html
<div id="root"></div>
<div id="modal-root"></div>

The modal was put into #modal-root using react portals in src/components/Modal.js.

// src/components/Modal.js
import ReactDOM from 'react-dom';
import styles from '../styles/Modal.module.css';

const Modal = ({ isOpen, onClose }) => {
  if (!isOpen) return null;
  return ReactDOM.createPortal(
    <div className={styles.modal}>
      Modal
      <button className={styles.btn} onClick={onClose}>
        x
      </button>
    </div>,
    document.querySelector('#modal-root')
  );
};

export default Modal;
// src/App.js
import { useState } from 'react';
import Modal from './components/modal';
import styles from './styles/App.module.css';

const App = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);

  return (
    <div>
      <button className={styles.btn} onClick={() => setIsModalOpen(true)}>
        Open Modal
      </button>
      <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
    </div>
  );
};

export default App;

image.png

The modal is opened with the button (a local state variable in App.js is used to track if the modal is opened or not), and is closed with a button inside the modal.

Positioning the Modal

This will position the modal in a fixed position on top of the content

// Modal.module.css
.container {
  position: fixed;
  top: 50px;
  left: 50%;
  background-color: white;
  border: 2px solid black;
  width: 50%;
  transform: translateX(-50%);
  padding: 10px;
  width: 50vw;
  height: 50%;
  z-index: 1000;
  overflow: auto;
}

Blurring the Background and Disable scrolling

A filter is used to set the background opacity when the modal is opened and the body overflow property is set to 'hidden' to disable scrolling. When the modal is closed, they will be set back to 100% and 'unset' respectively.

// src/App.js
const openModal = () => {
    setIsModalOpen(true);
    document.getElementById('root').style.filter = 'opacity(50%)';
    document.body.style.overflow = 'hidden';
  };

  const closeModal = () => {
    setIsModalOpen(false);
    document.getElementById('root').style.filter = 'opacity(100%)';
    document.body.style.overflow = 'unset';
  };

Now, we use openModal instead of setIsModalOpen(true), and closeModal instead of setIsModalOpen(false)

// src/App.js
<button className={styles.btn} onClick={openModal}>
     Open Modal
</button>
<Modal isOpen={isModalOpen} onClose={closeModal} />

image.png

Closing the Modal by Clicking Outside of the Modal

A ref, and an onClick handler are used to detect when the user clicks outside of the modal (but inside the App container). The modal will only close if it is currently opened. We can make the App container 100vh to cover the height of the browser.

const rootRef = createRef();
const handleClick = (e) => {
    if (rootRef.current.contains(e.target) && isModalOpen) {
      closeModal();
    }
};
<div ref={rootRef} onClick={handleClick}>

Now, App.js becomes

// App.js
import { createRef, useState } from 'react';
import Modal from './components/modal';
import styles from './styles/App.module.css';

const App = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);
  const rootRef = createRef();

  const openModal = () => {
    setIsModalOpen(true);
    document.getElementById('root').style.filter = 'opacity(50%)';
    document.body.style.overflow = 'hidden';
  };

  const closeModal = () => {
    setIsModalOpen(false);
    document.getElementById('root').style.filter = 'opacity(100%)';
    document.body.style.overflow = 'unset';
  };

  const handleClick = (e) => {
    if (rootRef.current.contains(e.target) && isModalOpen) {
      closeModal();
    }
  };

  return (
    <div className={styles.container} ref={rootRef} onClick={handleClick}>
      <button className={styles.btn} onClick={openModal}>
        Open Modal
      </button>
      <Modal isOpen={isModalOpen} onClose={closeModal} />
      <div>What is Lorem Ipsum?</div>
    </div>
  );
};

export default App;