React createPortals: A Step by Step guide with examples
React createPortals a step by step guide with examples

React createPortals: A Step by Step guide with examples

Dead Simple Chat Team

Table of Contents

React createPortal is a function that lets you render some children into a different part of the DOM

<div>
  <TestComponent />
  {createPortal(children, domNode, key?)}
</div>

This can be useful when you want to render some components on top of another component

Some examples include dropdown menus, tooltips etc

If you are looking for a React Native Chat SDK to build in app chat messaging application, you can consider DeadSimpleChat React Native SDK

How does createPortal work?

you can create a portal by calling the createPortal function an

A portal changes the physical placement of the DOM node. But in all other ways the JSX you render through the portal acts as a child of the component that is rendering it

As an example:

the events will bubble up from the child to the parent and the child can also access the context provided by the parent.

import { createPortal } from 'react-dom';

// ...

<div>
  <p>This child inside div.</p>
  {createPortal(
    <p>This child is inside body.</p>,
    document.body
  )}
</div>

Parameters

  • Children : a <div /> tag or string or an array or some JSX basically anything that can be rendered with react can be children and passed to the createPortal function as a prop
  • domNode :  A DOM node, such as those returned by document.getElementById() The node must already exist. Portal content will be recreated if a DOM node is passed during an update
  • optional Key: A unique key or a string that is used as the portals key

Returns

createPortal returns a react node that can be included  in to JSX or returned from a React component

Caveats

  • Events from the portal propogate according to the react tree and not according to the DOM tree.
  • This could cause issues like If you have an onClick on a div the onClick event handler function will fire. If this causes issues either stop the event propagation from inside the portal or move the portel up the React tree

Let us consider some examples of the React portal

Dead Simple Chat allows you to easily add Chat to any React Application using powerful Javascript Chat SDK.

Example 1: Rendering a p tag from inside a div to body tag using Portal

Portal lets you place a DOM node and its children in a different part of the DOM. It creates a Portal through which the element can travel to a different part.

Let us take an example where a modal dialog might appear on top of the rest of the page

To create a portal call the create portal function with some JSX that you want to render and the DOM node where you want to render it

ReactDOM.createPortal(JSX that you want to render, DOM node);

React will put the DOM nodes for the JSX that you provided inside the DOM node you provided

import { createPortal } from "react-dom";

function TestComponent() {
  return (
    <div style={{ border: "2px solid black" }}>
      <p>This child is inside the above div.</p>
      {createPortal(
        <p>This child is inside document body.</p>,
        document.body
      )}
    </div>
  );
}

Without the portal the second p tag will be inside the div but the portal transported it to be inside the body tag

import { createPortal } from "react-dom";

export default function TestComponent() {
  return (
    <div style={{ border: "5px solid red" }}>
      <p>This child is inside the above div.</p>
      {createPortal(
        <p>This child in the document body html tag.</p>,
        document.body
      )}
    </div>
  );
}
test component

Notice that the second heading is outside the box styling that the div has. Thus proving that the second heading is outside the div and in the document body tag

If you inspect element the div you will see that the second element is outside the div and inside the body tag. The portal transported the element from the div to the body.

Dead Simple Chat allows you to easily add Chat to any React Application using powerful Javascript Chat SDK.

Example 2: Rendering a Modal dialog with Portal

This is an advanced example: we have a modal dialog that is inside a container with overflow: hidden property on it and other styling that is interfering with the dialog

and we need to transport the dialog above everything else and it should float above the rest of the page

I will explain what is happening just scroll below the code:

import NoPortalExample from "./NoPortalExample";
import PortalExample from "./PortalExample";

export default function App() {
  return (
    <>
      <div className="clipping-container">
        <NoPortalExample />
      </div>
      <div className="clipping-container">
        <PortalExample />
      </div>
    </>
  );
}
App.js
export default function ModalContent({ onClose }) {
  return (
    <div className="modal">
      <div>It is a modal </div>
      <button onClick={onClose}>Close</button>
    </div>
  );
}
ModalContent.js
import { useState } from 'react';
import ModalContent from './ModalContent.js';

export default function NoPortalExample() {
  const [showModal, setShowModal] = useState(false);
  return (
    <>
      <button onClick={() => setShowModal(true)}>
        Show modal without a portal
      </button>
      {showModal && (
        <ModalContent onClose={() => setShowModal(false)} />
      )}
    </>
  );
}
NoPortalExample.js
import { useState } from 'react';
import { createPortal } from 'react-dom';
import ModalContent from './ModalContent.js';

export default function PortalExample() {
  const [showModal, setShowModal] = useState(false);
  return (
    <>
      <button onClick={() => setShowModal(true)}>
        Show modal using a portal
      </button>
      {showModal && createPortal(
        <ModalContent onClose={() => setShowModal(false)} />,
        document.body
      )}
    </>
  );
}
PortalExample.js
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";

import App from "./App";

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
index.js
{
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-scripts": "^5.0.0"
  },
  "main": "/index.js",
  "devDependencies": {}
}
package.json
* {
  box-sizing: border-box;
}

body {
  font-family: sans-serif;
  margin: 20px;
  padding: 0;
}

h1 {
  margin-top: 0;
  font-size: 22px;
}

h2 {
  margin-top: 0;
  font-size: 20px;
}

h3 {
  margin-top: 0;
  font-size: 18px;
}

h4 {
  margin-top: 0;
  font-size: 16px;
}

h5 {
  margin-top: 0;
  font-size: 14px;
}

h6 {
  margin-top: 0;
  font-size: 12px;
}

code {
  font-size: 1.2em;
}

ul {
  padding-left: 20px;
}

.clipping-container {
  position: relative;
  border: 1px solid #aaa;
  margin-bottom: 12px;
  padding: 12px;
  width: 250px;
  height: 80px;
  overflow: hidden;
}

.modal {
  display: flex;
  justify-content: space-evenly;
  align-items: center;
  box-shadow: rgba(100, 100, 111, 0.3) 0px 7px 29px 0px;
  background-color: white;
  border: 2px solid rgb(240, 240, 240);
  border-radius: 12px;
  position:  absolute;
  width: 250px;
  top: 70px;
  left: calc(50% - 125px);
  bottom: 70px;
}
styles.css

So, in this example. let us look at each of the file individually

App.js Here we are importing the NoPortalExample and the PortalExample components

  • inside the App.js there are two div elements with class clipping-container given to each of them

modalContent.js : We have a ModalContent component that receives an onClose function on prop which will be called when the user clicks the close button

NoPortalExample.js We have a showModal to determine modal should be shown or not

when the showModal without a button is clicked, the state of the showModal is set to true

ModalContent component is rendered directly as a child of the NoPortalExample when the showModal component is set to true

PortalExample.js When the Show Modal using Portal button is clicked the state of the showModal is set to true

ModalComponent is rendered using the Portal. the portal function is given the jsx for the Modal that is to be rendered and the DOM element where it is to be rendered that is the document body element

Show Modal with Portal
Show Modal without a portal

Dead Simple Chat allows you to easily add Chat to any React Application using powerful Javascript Chat SDK.

Example 3: Non-React Server Markup: Rendering React components using Portal

Portals can be useful if your React Root is only part of the static or server-rendered page that is not completely built with react

For example If you application is built with NodeJs and express you can create some areas of interactivity within areas of that are static such as sidebars

Portals let you treat the app as a single React tree with shared state even though its parts render to different parts of the DOM.

import { createPortal } from 'react-dom';

const sidebarContentEl = document.getElementById('sidebar-content');

export default function App() {
  return (
    <>
      <MainContent />
      {createPortal(
        <SidebarContent />,
        sidebarContentEl
      )}
    </>
  );
}

function MainContent() {
  return <p>Part 1 will not be transported</p>;
}

function SidebarContent() {
  return <p>This 2 part will be transported </p>;
}

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
<!DOCTYPE html>
<html>
  <head><title>My app</title></head>
  <body>
    <h1>Welcome to my hybrid app</h1>
    <div class="parent">
      <div class="sidebar">
        This is server non-React markup
        <div id="sidebar-content"></div>
      </div>
      <div id="root"></div>
    </div>
  </body>
</html>

What are we doing here

we are creating a const sidebarContentEl and assigning it the value of sidebar-content id which is a div assigned in the index.html file

then we are calling  the createPortal function with the props of SideBarContent and sidebarContenEl

that will transport the SideBarContent component to the sidebar div that is inside the div with the class of sidebar

Dead Simple Chat allows you to easily add Chat to any React Application using powerful Javascript Chat SDK.

Example 4: Rendering React components in to non react dom nodes

DOM nodes that are outside of react can also be managed using the react portal method.

We have a non react map widget and we want to have a pop up with react content on top of it.

const [popupContainer, setPopupContainer] = useState(null);

When creating third party widget always remember to store the DOM node that is returned by the widget.

So that you can render it.

useEffect(() => {
  if (mapRef.current === null) {
    const map = createMapWidget(containerRef.current);
    mapRef.current = map;
    const popupDiv = addPopupToMapWidget(map);
    setPopupContainer(popupDiv);
  }
}, []);

this lets you render react content using portal into the popupContainer.

return (
  <div style={{ width: 250, height: 250 }} ref={containerRef}>
    {popupContainer !== null && createPortal(
      <p>Hi where are we!</p>,
      popupContainer
    )}
  </div>
);

Here is the complete code:

import { useRef, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { createMapWidget, addPopupToMapWidget } from './map-widget.js';

export default function Map() {
  const containerRef = useRef(null);
  const mapRef = useRef(null);
  const [popupContainer, setPopupContainer] = useState(null);

  useEffect(() => {
    if (mapRef.current === null) {
      const map = createMapWidget(containerRef.current);
      mapRef.current = map;
      const popupDiv = addPopupToMapWidget(map);
      setPopupContainer(popupDiv);
    }
  }, []);

  return (
    <div style={{ width: 500, height: 500 }} ref={containerRef}>
      {popupContainer !== null && createPortal(
        <p>Hi where are we!</p>,
        popupContainer
      )}
    </div>
  );
}
App.js
import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet';

export function createMapWidget(containerDomNode) {
  const map = L.map(containerDomNode);
  map.setView([0, 0], 0);
  L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '© OpenStreetMap'
  }).addTo(map);
  return map;
}

export function addPopupToMapWidget(map) {
  const popupDiv = document.createElement('div');
  L.popup()
    .setLatLng([0, 0])
    .setContent(popupDiv)
    .openOn(map);
  return popupDiv;
}
map-widget.js
map widget displayed

Conclusion

In this article I have explained how the react Portal works. It transports the elements from one part of the DOM to another

It really is an handy tool when developing applications that have complex components on top of one another and you need to transport the component form one part of the DOM tree to another

Remember though the component is still part of the react tree and the events will still bubble up the tree. If you do not want the events to bubble up implement stop propagation in the component that is being sent via the portal

Thanks for reading