Updated on January 29, 2023

Creating an Efficient Modal Component in React using Hooks and Portals

9 min read
0 views
cover image for a blog on Creating an Efficient Modal Component in React using Hooks and Portals
Modal is a common UX element. A modal is a dialog box/popup window that is displayed on top of the current page. You must have used pop-ups and notifications for your website. For some people pop-ups are really annoying😫 if it doesn’t behave as it should. It must have good UI/UX.

In this article we’re going to create an efficient Modal component🤩 from scratch without using any library.

Demo Link🖤: Modal using React Portals

Our main goal is to create an efficient modal which,

  • Has a good layout
  • Doesn’t break ui when we use overflow in parent component
  • Can render content Dynamically
  • Clean and Elegant animation
  • Looks good (good UI)
  • Have more control for User (like clicking outside can close modal) etc.

If you prefer video format then you can watch this video 📽👇

Let’s get started!

Setting Up The Project

Create your React App using,

npx create-react-app react-modal

For this tutorial i’m going to use Sass so make sure you have installed node-sass package. To install it do,

npm install node-sass

Setting up the basic Modal structure

Open the App.js file and clean🧹 unnecessary code. Now let’s create one button in the App.js file to open and close modal just like given in the following code 👇

<div className="App">
  <button>Modal</button>
</div>

Let’s create one state to open and close modal. Write the following 👇 code:

App.js
1import { useState } from "react";
2import "./App.scss";
3
4function App() {
5
6  const [modal, setModal] = useState(false);
7  const Toggle = () => setModal(!modal);
8
9  return (
10    <div className="App">
11      <button className="clickme" onClick={() => Toggle()}>
12        Modal
13      </button>
14    </div>
15  );
16}
17
18export default App;
19

Line 6: This line contains modal state which is false initially.

Line 7: A Toggle method to toggle modal state from false to true and vice-versa.

Line 11: Make sure to connect Toggle() method to onClick of the button.

Next create Modal.js file and Write the following 👇 code:

Modal.js
const Modal = () => {
  return (
    <div>
      Modal
    </div>
  );
};

export default Modal;

Now let’s import Modal in the App.js file.

App.js
1import { useState } from "react";
2import "./App.scss";
3import Modal from "./Components/Modal";
4
5function App() {
6
7  const [modal, setModal] = useState(false);
8  const Toggle = () => setModal(!modal);
9
10  return (
11    <div className="App">
12      <button className="clickme" onClick={() => Toggle()}>
13        Modal
14      </button>
15      <Modal show={modal} />
16    </div>
17  );
18}
19
20export default App;
21

Line 17: Here we have imported Modal component. And passed modal state as show in the props.

Now open Modal.js and add the following code 👇

Modal.js
1import Close from "./times-solid.svg";
2
3const Modal = ({ show }) => {
4  return (
5    <>
6     {
7     show ?
8
9     <div
10        className="modalContainer"
11      >
12        <div className="modal" >
13          <header className="modal_header">
14            <h2 className="modal_header-title"> Modal Title </h2>
15            <button className="close" >
16              <img src={Close} alt="close" />
17            </button>
18          </header>
19          <main className="modal_content">
20          This is Modal Content
21          </main>
22          <footer className="modal_footer">
23            <button className="modal-close" >
24              Cancel
25            </button>
26
27            <button className="submit">Submit</button>
28          </footer>
29        </div>
30      </div>
31      : null
32     }
33    </>
34  );
35};
36
37export default Modal;
38

Line 3: Deconstruct show from the props.

Line 7: We will display modal only when show state is true.

Line 9 to 30: This is the whole Modal layout.

  • ModalContainer div contains the modal
  • In the modal div, There is one header which contains modal title and close Button (You can use any icon for close button).
  • Main tag contains content of the modal.
  • Footer has 2 buttons one is submit and another is cancel.

Styling The Modal

Now when you press a button, the modal will show and on pressing again it will close the modal. First Let’s add some styling to our modal. Create Modal.scss file and import it in the Modal.js file. Copy and paste the following styles in the Modal.scss file.

Modal.scss
.modalContainer {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba($color: #000000, $alpha: 0.35);

  display: flex;
  justify-content: center;
  align-items: center;

  .modal {
    width: 30vw;
    height: auto;
    background-color: #fff;
    padding: 2rem;
    border-radius: 20px;

    background-color: rgba(255, 255, 255, 0.35);
    backdrop-filter: blur(5px);
    box-shadow: 0 0 1rem 0 rgba(0, 0, 0, 0.2);

    &_header {
      position: relative;
      border-bottom: 1px solid #dddddd;
      &-title {
        text-align: center;
      }

      .close {
        position: absolute;
        top: 0;
        right: 0;
        background: transparent;

        img {
          width: 1rem;
          height: auto;
          transition: all 0.3s;
        }
        &:hover {
          img {
            transform: scale(1.1);
          }
        }
      }
    }

    &_content {
      border-bottom: 1px solid #dddddd;
      padding: 2rem 0;
    }

    &_footer {
      padding: 2rem 0;
      padding-bottom: 0;

      button {
        float: right;
        padding: 0.5rem;

        border-radius: 8px;
      }
      .modal-close {
        background-color: transparent;
        font-weight: 600;

        &:hover {
          color: rgba(54, 67, 72, 0.8);
        }
      }
      .submit {
        margin-right: 1rem;
        background-color: #364348;
        color: #fff;

        &:hover {
          background-color: rgba(54, 67, 72, 0.8);
        }
      }
    }
  }
}

This will give your modal a better look. Also, by applying the backdrop-filter you can make it look like frost-glass.

Adding a Close Event in Modal

In the App.js file pass toggle method as a props in the modal just like the following line 👇

<Modal show={modal} title="My Modal" close={Toggle} />

open Modal.js file and deconstruct close from the props.

Modal.js
1import Close from "./times-solid.svg";
2
3const Modal = ({ show, close }) => {
4  return (
5    <>
6     {
7     show ?
8
9      <div
10        className="modalContainer"
11        onClick={() => close()}
12      >
13        <div className="modal" onClick={(e) => e.stopPropagation()}>
14          <header className="modal_header">
15            <h2 className="modal_header-title">Modal Title</h2>
16            <button className="close" onClick={() => close()}>
17              <img src={Close} alt="close" />
18            </button>
19          </header>
20          <main className="modal_content">This is modal content</main>
21          <footer className="modal_footer">
22            <button className="modal-close" onClick={() => close()}>
23              Cancel
24            </button>
25
26            <button className="submit">Submit</button>
27          </footer>
28        </div>
29      </div>
30      : null
31     }
32    </>
33  );
34};
35
36export default Modal;
37

Line 3: Deconstruct close from the props.

We have added close method in 3 places:

Line 16: At the close button.

Line 22: At the cancel button.

Line 11: We have also added close method here too. Why? because whenever user clicks outside it should close the modal. So here, when user clicks on the modalContainer it closes the modal.

Line 13: here we have to stop this click events in the modal else it will close it so for that we have used e.stopPropagation().

hint: You can also add event Listener and add functionality in which when user clicks esc key, It closes the modal. (It is good for user experience)

Using Portals to render the Modal

What🧐 is portals ?

  • Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

Why🤔 should we use portals?

  • Sometimes when we use overflow or z-index properties in the parent component then we need child component (like modal or dialogues) to break container visually, and portals can be very handy to do that because it renders children outside of DOM hierarchy.

Syntax✍ for to create portal👇

ReactDOM.createPortal
(element, DOM node where you want to render this element)

So let’s implement portals in our Modal component. To use portal we have to create one more element in the dom. Generally our whole App renders in the div with the id root.

Open index.html file and above the root div create one more div with the id modal. Just like the following code block👇

<div id="modal" />
<div id="root" />

Let’s update the ‘Modal.js’ file.

Modal.js
1import ReactDOM from "react-dom";
2import "./modal.scss";
3import Close from "./times-solid.svg";
4
5const Modal = ({ show, close }) => {
6  return ReactDOM.createPortal(
7    <>
8     {
9     show ?
10
11      <div
12        className="modalContainer"
13        onClick={() => close()}
14      >
15        <div className="modal" onClick={(e) => e.stopPropagation()}>
16          <header className="modal_header">
17            <h2 className="modal_header-title">Modal Title</h2>
18            <button className="close" onClick={() => close()}>
19              <img src={Close} alt="close" />
20            </button>
21          </header>
22          <main className="modal_content">This is modal content</main>
23          <footer className="modal_footer">
24            <button className="modal-close" onClick={() => close()}>
25              Cancel
26            </button>
27
28            <button className="submit">Submit</button>
29          </footer>
30        </div>
31      </div>
32      : null
33     },
34    document.getElementById("modal")
35    </>
36  );
37};
38
39export default Modal;
40

Line 1: let’s import ReactDom.

Line 6: After return let’s create portal using ReactDom.createPortal, As it’s first argument we have passed whole modal component and for the second argument we have passed the dom node where we want to render it.

Line 34: We want to render our component in the div with id modal.

Adding dynamic content to the Modal

open App.js file and pass title as a props and content inside the component as shown below,

<Modal show={modal} title="My Modal" close={Toggle}>
  This is Modal content
</Modal>

Here we have passed title as props and modal content as the children. Here is the final code for modal component.

Modal.js
1import ReactDOM from "react-dom";
2import "./modal.scss";
3import Close from "./times-solid.svg";
4
5const Modal = ({ show, close, title, children }) => {
6  return ReactDOM.createPortal(
7    <>
8     {
9     show ?
10
11      <div
12        className="modalContainer"
13        onClick={() => close()}
14      >
15        <div className="modal" onClick={(e) => e.stopPropagation()}>
16          <header className="modal_header">
17            <h2 className="modal_header-title"> {title} </h2>
18            <button className="close" onClick={() => close()}>
19              <img src={Close} alt="close" />
20            </button>
21          </header>
22          <main className="modal_content"> {children} </main>
23          <footer className="modal_footer">
24            <button className="modal-close" onClick={() => close()}>
25              Cancel
26            </button>
27
28            <button className="submit">Submit</button>
29          </footer>
30        </div>
31      </div>
32      : null
33     },
34    document.getElementById("modal")
35    </>
36  );
37};
38
39export default Modal;
40

Line 5: Let’s deconstruct title and children from the props.

Line 17: Here, insert title in the curly braces.

Line 22: Let’s render the children using the curly braces.

Now if you want to add a little animation in the modal you can watch the video or you can go to the git repository and read📚 code.

Full code for this tutorial👉 create Modal using React Portals

Like this article? Share this on👇