How to create modal using React Portal?
Tags: webdev, react
Published: 2022-06-15 18:30:12

Hello everyone, in this first blog of mine I am going to show how to create modal using React Portal. Modal at first seems to be another React component we can make but implementing a modal properly teaches us a cool concept in React which is React Portals.
<br>
**First question, what is React Portal?**

Quoting straight from the [docs](https://reactjs.org/docs/portals.html) it says -

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

Now, let's try to get what rendering outside the parent component means and how Portal solve this issue? 

When we render any component in react it is rendered inside the parent component which forms a tree like hierarchy. In some cases we might want to render our component to a completely different node which is outside our parent. This is where this special feature of react helps.
<br>
**Second question, why do we need a Portal for a modal?**

Modal is something which is expected to open on top of everything. If we render the modal within the page(component) we are on then it will be not easy to write CSS such that the modal is on top of everything and in centre.

Sure, we can do something like this(which I have seen some developers doing) at the outer most layer.

```js
export default function App() {
  const [modalOn, setModalOn] = useState(true);
  return (
    <div>
      {modalOn && <ConfirmationModal />}
      //rest of the app
      //
    </div>
  );
}
```
Using this method we can show/hide the modal component from the outermost layer and pass the props down or use something like redux or context API for keeping the modal state global.

Sure, this approach works but it has some flaws

- There is a need to keep the state global for modal/ pass the props down
- The modal component need to be defined away from the component we are on, the Modal cannot reside inside same component.
<br>
**This is where React Portal comes to rescue**
Now let's try to understand how to implement a modal and see how it works.

1) **Add a div to index.html for modal.** This will be the modal root where we will be rendering our modals.

```html
<div id="portal-root"></div>
```
<br>
2) **Create a `PortalForModal` component.**

```jsx
import "./portal-for-modal.css";
import { useEffect, useRef } from "react";
import { createPortal } from "react-dom"; //import createPortal

export const PortalForModal = ({ children, dismiss }) => {
  const elRef = useRef(null); //initilaize with null
  if (!elRef.current) {
    elRef.current = document.createElement("div");
  }

  useEffect(() => {
    const portalRoot = document.getElementById("portal-root");
    portalRoot.appendChild(elRef.current);
    //appending the portal first time component is rendered.

    return () => {
      portalRoot.removeChild(elRef.current);
      //cleanup- removing the node to prevent memory leak.
    };
  }, []);

  return createPortal(
    <div//the div covering entire screen where we can place click
    //listener for the modal to close (this is optional)
      className="portal-overlay"
      onClick={() => {//placing a click listener to this div to 
        //close the modal
        dismiss((showModal) => !showModal);
      }}
    >

      <div className="portal" onClick={(e) => e.stopPropagation()}>
        {children} 
      </div>
    </div>,
    elRef.current// the dom node we want to render to(here- portal-root)
  );
};

```

I understand, I understand that this is too much code at once. Let's try to get what component is about and what it is doing?

- `PortalForModal` can be said as a wrapper component which when wrapped around any component will render the enclosed components inside a React Portal. It receives two props children and the dismiss function(used to close the modal).
<br>
- `elRef` is the container that will be holding our Portal. We instantiate it with a `div`.
<br>
- Upon the first render of component, we are appending our portal container(elRef.current) to the DOM node(here, portalRoot) using `appendChild`. We are also doing the necessary clean-up function when the component un-mounts i.e. removing the container from the DOM using `removeChild`.

- Finally we are using the magical functional provided by react that is `createPortal`. It takes the two arguments child and container. 

- Our child here is a `div` element with class name 'portal-overlay'. Inside this div we have another div with class-name 'portal' which renders our children passed down into our `PortalForModal` component.
 
- The container is the `elRef.current` which is simply a div which we created.

- The div spanning the whole page, one with class-name of 'portal-overlay' has also a click listener on it. Clicking on this div invokes the dismiss function which was also passed as a prop into the component.
<br>
3) **Add some CSS styling to our component.** Include a file named 'portal-for-modal.css'.I will not be covering CSS in detail here.

```css
.portal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  padding: 50px;
  border: 2px solid;
}
.portal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background: rgba(255, 255, 255, 0.5);
  backdrop-filter: blur(5px);
}

```
All this CSS is doing is 
- Spanning a background div throughout the page and giving it a background blur.
- Centering the portal div to the exact of page using fixed positioning and translate.
<br>

4) **Finally get to use the Portal component for our Modal!**

```jsx
export default function App() {
  const [modalOn, setModalOn] = useState(false);
  return (
    <div className="App">
      <button id="button" onClick={() => setModalOn((prev) => !prev)}>
        Show Modal
      </button>
      <div id="big-text">This is an example of modal using react portals.</div>
      {modalOn && (
        <PortalForModal dismiss={setModalOn} 
         //everything inside this will be rendered inside portal at center of screen.
         >

          <p style={{ textAlign: "center", margin: "1rem" }}>
            This modal is rendered on a dom node outside the current root node.
          </p>
          <p style={{ textAlign: "center", margin: "1rem" }}>
            You can click the below button or area outside this modal to close
            it.
          </p>
          <button
            onClick={() => {
              setModalOn((prev) => !prev);
            }}
          >
            Close Modal
          </button>
        </PortalForModal>
      )}
    </div>
  );
}
```

- `modalOn` is a boolean react state which decides if the modal will be shown or not.
- Passing the `setModalOn` as `dismiss` prop to Portal component.
- Rendering whatever we want inside our modal(here, a button and two `<p>`'s) without defining them somewhere else.
<br>

5) **Ultimately, the code demo**

![Linus's quote](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvq4xo21cgroln81tpuj.png)
[image source](https://quotefancy.com/quote/1445782/Linus-Torvalds-Talk-is-cheap-Show-me-the-code)

{% codesandbox https://codesandbox.io/embed/react-modal-forked-mpbubg?fontsize=14&hidenavigation=1&theme=dark %}
<br>
Hope you enjoyed my first blog. Please do leave your feedback and suggestions if you read it till here. 

Good Luck!