Dynamic React Portals with hooks

Juan Carlos Pérez
2 min readApr 8, 2019

--

I struggled finding a good example online on how to create a Portal which renders into a DOM element, uniquely identified with an id. In which we have 2 existing possibilities:

  • The DOM element already exists in the DOM tree.
  • The DOM element doesn’t exist and needs to be created.

Where can we use a dynamic Portal?

If your app could conflict between displaying a popup on the top of another popup. (second popup could be a dynamic portal).

If your app needs to display an important message in a fixed banner (your banner would probably fit well in a dynamic portal).

The code

I created a simple quick example on how to use this implementation. 👈 Please feel free to add comments.

The actual code I managed to produce for the Portal implementation was:

import { memo, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
const Portal = ({ id, children }) => {
const el = useRef(document.getElementById(id) || document.createElement('div'));
const [dynamic] = useState(!el.current.parentElement)
useEffect(() => {
if (dynamic) {
el.current.id = id;
document.body.appendChild(el.current);
}
return () => {
if (dynamic && el.current.parentElement) {
el.current.parentElement.removeChild(el.current);
}
}
}, [id])
return createPortal(children, el.current);
};
export default memo(Portal);

This 21 line snippet has the following features:

  • It creates a Portal in a DOM element identified with an id prop already attached to the DOM tree.
  • It creates a Portal in a new DOM element that will be identified by the id prop when there is no elements in the DOM tree using that id.
  • When the Portal id prop changed in an already mounted Portal it will remove the previous DOM element identified by theid only when that element was created while the Portal was mounted.

Key Gotchas

const el = useRef(document.getElementById(id) || document.createElement('div'));

On every render we read from the DOM to get the element by id. In case it doesn’t exist, we create an empty div.

const [dynamic] = useState(!el.current.parentElement)

In that line, what does dynamic really means? It means the DOM element used in the portal is dynamically created and needs to be dynamically destroyed when the component unmounts.

We use a state that won’t change though the life of the component, but it will important to know whether we remove it from the DOM when unmounting.

useEffect(() => {
}, [id])

When by any chance the id prop value changes, we need our component to react to it by updating the el reference to a dynamic element or from a dynamic element.

And that’s it! Please leave comments if you see a potential bug or just to open a discussion regarding this implementation.

--

--

Juan Carlos Pérez

Montreal #traveler 🍁, #Mexican proud 🌮, #javascript developer ⚛️