DEV Community

Cover image for Transporting your Components Anywhere with React Portals
Johnny Simpson
Johnny Simpson

Posted on • Originally published at fjolt.com

Transporting your Components Anywhere with React Portals

When we create components in React, normally they exist within the component tree. This is mostly fine, but sometimes we want certain parts of a component to appear outside the component tree, or somewhere entirely different. This is a common requirement when we create modal popup windows, which need to be above all other components. We may create these within a component, but ultimately we'll want them above everything else, and having them nested within many components can can cause issues as their z-index will fall below whatever they are within:

React Component Diagram

To solve this problem, we can teleport the modal out of its own component and into another part of our template using createPortal. This allows us to put our component anywhere we desire else, like the base of the HTML tree, inside the body tag, or inside another element. Even though the element exists within the component tree, createPortal gives us the power to put it anywhere we like.

Using React Portals

To show you how portals work, consider we have the following basic React code inside our App.js file. Here, we want the modal to appear on top of everything else. As such, we've created a div called #modal-container. This is ultimately where we want all of our modals to go into:

import logo from './logo.svg';
import './App.css';
import { useState } from 'react'
import Modal from './components/Modal.js';

function App() {
    const [isModalOpen, setIsModalOpen] = useState(false);
    return (
        <div className="App">
            <header className="App-header">
                <img src={logo} className="App-logo" alt="logo" />
                <p>
                Edit <code>src/App.js</code> and save to reload.
                </p>

                <button onClick={() => setIsModalOpen(!isModalOpen)}>
                    Click to Open Modal
                </button>
                <Modal modalState={isModalOpen} onClickEvent={() => setIsModalOpen(!isModalOpen)}>
                    This is Modal Content!
                </Modal>
            </header>
            <div id="modal-container"></div>
        </div>
    );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Inside App.js, I've imported a component called Modal. This is our Modal component, which will pop up any time the user clicks the button. Whenever isModalOpen is set to true using setIsModalOpen(), the modal should appear. Otherwise, it'll disappear.

I've also got a bit of CSS to ensure our modals do indeed appear on top of everything else:

#modal-container {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 9999;
    height: 100%;
    pointer-events: none;
}

.modal {
    position: absolute;
    top: 200px;
    background: white;
    border-radius: 4px;
    left: calc(50% - 100px);
    width: 200px;
}
Enter fullscreen mode Exit fullscreen mode

Creating our portal

Creating a potal is pretty easy - there is one function, createPortal(). Instead of returning some DOM in React, we return the Portal instead. createPortal() accepts two arguments - the DOM element we want to return - in this case, the modal - and the DOM element we want to teleport our DOM element to. So our second argument is document.getElementById('modal-container'), since we want to put all of our modals into #modal-container:

import { createPortal } from 'react-dom';

function Modal({modalState, onClickEvent}) {

    if(!modalState) return null;

    return (
        createPortal(
            <div className="modal">
                <button onClick={onClickEvent}>Close Modal</button>
                <div className="modal-content">Modal Content goes here</div>
            </div>, 
            document.getElementById('modal-container')
        )
    );
};

export default Modal;
Enter fullscreen mode Exit fullscreen mode

Although we teleported our DOM element to modal-container, it still behaves like a normal React child. Since the Portal still exists in the React tree, features like the context the element is in still work the same.

It should also be noted that although we have modal-container and Modal in the same file, the place you teleport your DOM element to can be anywhere in your React code. So you could teleport it to a completely different sub component, element, or parent anywhere in the DOM. It's pretty powerful, and useful - so use it wisely.

Let's look back at our App.js HTML:

    <!-- .... -->
    <button onClick={() => setIsModalOpen(!isModalOpen)}>
        Click to Open Modal
    </button>
    <Modal modalState={isModalOpen} onClickEvent={() => setIsModalOpen(!isModalOpen)}>
        This is Modal Content!
    </Modal>
</header>
<div id="modal-container"></div>
Enter fullscreen mode Exit fullscreen mode

Now, even though Modal sits in our header, it will appear in #modal-container whenever we open the modal using the button:
How Portals work in React

Conclusion

Portals are a pretty powerful tool in React. They are a useful way to solve the main issue with component based systems - transporting certain elements above all the rest. As such, I hope you've enjoyed this guide to React portals. If you are learning React, I'd suggest mastering Javascript first - which you can do with my full Javascript Handbook.

Have a great day.

Top comments (2)

Collapse
 
boredcity profile image
boredcity

First off, I hope I understood the questions correctly and sorry if I didn't :)

You usually want modals and buttons (or other elements responsible for opening modals -- I'll refer to them as "Modal Openers") in different places in the DOM (and also the React Component) tree:

Modals are usually at the root for several reasons, for example so they can be positioned more easily there, avoid problems with other elements covering them. It also makes sure that you can limit the number of modals that can be opened simultaneously. So that is usually somewhere not too far from the application's root component.

Modal Openers can be anywhere and often are quite deep in the component tree.

If you open the Modal and want to pass some data to it you would have to pass a openModal callback to each Modal Opener prop drilling to them from the place where Modal is rendered. The other option is using a state manager (like Redux, MobX or whatever; even Context will work) -- but then you add another level of complexity to the app.

When you use Portals it's much simpler: you just create Modal's content inside the Modal Opener having access to all its props -- and then say "hey, I want it rendered elsewhere". This is often a better abstraction than a complex system relying on a separate state manager (or prop drilling).

The other use case is when you don't have access to the whole page's HTML, but have to build several widgets with a shared state, I think.

Collapse
 
jai_type profile image
Jai Sandhu • Edited

Do portals work with framer motion's <AnimatePresence>?