DEV Community

Cover image for React Portals: The Modal Problem That Taught Me Something New
Rahul Sharma
Rahul Sharma

Posted on

React Portals: The Modal Problem That Taught Me Something New

I was working on a React project where I needed to show a simple confirmation modal when a user clicked a button.

At first, it was easy. I created a Modal component, added some CSS, and everything looked perfect.

Then the application started getting bigger.

My button was inside a card, the card was inside a dashboard, and the dashboard was inside another container. Some of those parent elements had CSS properties like overflow: hidden and their own z-index values.

That's when my innocent little modal started causing problems. 😅

The modal was supposed to appear on top of the entire page, but instead, parts of it were getting clipped or appearing behind other elements.

So I did what probably every developer does first:

z-index: 9999;
Enter fullscreen mode Exit fullscreen mode

Didn't work.

So I tried:

z-index: 999999;
Enter fullscreen mode Exit fullscreen mode

Still didn't solve the real problem.

At that point, I realized that the problem wasn't really the number of z-index. The modal was simply living inside a DOM structure that was making its job difficult.

That's when I came across something called React Portal.


So, what is React Portal?

In simple terms, a Portal allows us to say:

"Keep this component as part of my React component, but render its HTML somewhere else in the DOM."

Normally:

App
 └── Dashboard
      └── Card
           └── Modal
Enter fullscreen mode Exit fullscreen mode

With a Portal, React can render the Modal somewhere like:

#root
 └── App
      └── Dashboard
           └── Card

#modal-root
 └── Modal
Enter fullscreen mode Exit fullscreen mode

index.html

The Modal is still part of the same React tree, but its actual DOM element is rendered inside #modal-root.


How do we do it?

First, create another DOM container:

<div id="root"></div>
<div id="modal-root"></div>
Enter fullscreen mode Exit fullscreen mode

Then use createPortal:

import { createPortal } from "react-dom";

createPortal(
  <Modal />,
  document.getElementById("modal-root")
);
Enter fullscreen mode Exit fullscreen mode

That's basically it.

portal example code


Why is this useful?

Portals are especially useful for UI that needs to escape its parent's layout, such as:

  • Modals
  • Dialogs
  • Tooltips
  • Dropdowns
  • Toast notifications
  • Full-screen overlays

Instead of fighting with deeply nested CSS and z-index, we can render these elements somewhere more appropriate in the DOM.


The thing that finally made Portals click for me

Before learning about Portals, I assumed:

If a component is inside another component, its HTML must also stay inside that component's DOM.

But that's not necessarily true.

React tree and DOM tree don't always have to be the same.

That's the whole idea behind React Portal.

Once I understood that, Portals stopped feeling like some complicated React feature.

It became a simple solution to a simple problem:

"I want this component to belong here in React, but I want its DOM to live somewhere else."

And sometimes, that's exactly what you need. 🚀

console ss

Understanding the #modal-root in this image

Looking at the image, you can see that there are two separate DOM containers:

<body>
 ├── <div id="root">
 │     └── React App
 │
 └── <div id="modal-root">
       └── Modal
Enter fullscreen mode Exit fullscreen mode

At first, this can be confusing.

You might think:

"If #modal-root is outside #root, then is the Modal now outside React? Will my state and click events stop working?"

No.

The important thing to understand is that DOM structure and React structure are not necessarily the same.

Normally, without a Portal, you might have:

React Tree + DOM Tree

App
 └── Account
      └── Modal
Enter fullscreen mode Exit fullscreen mode

But with createPortal():

React Tree

App
 └── Account
      └── Modal
Enter fullscreen mode Exit fullscreen mode

while the DOM Tree becomes:

DOM Tree

#root
 └── App
      └── Account

#modal-root
 └── Modal
Enter fullscreen mode Exit fullscreen mode

So the Modal's HTML has moved, but the Modal is still connected to the same React component.

That's why things like:

  • useState
  • props
  • context
  • onClick
  • other React logic

still work normally.

For example:

createPortal(
  <Modal onClose={() => setShowModal(false)} />,
  document.getElementById("modal-root")
);
Enter fullscreen mode Exit fullscreen mode

Even though the Modal is physically rendered inside #modal-root, it can still call setShowModal(false) from the React component that created it.

The main idea

A Portal changes where the component is rendered in the DOM, not where it belongs in the React tree.

So in the image, #modal-root is separate in the DOM, but the Modal is still part of the React application and its React logic continues to work.

Top comments (0)