Skip to main content

Portals

Using ReactDOM.createPortal:

const modalRoot = document.getElementById("modal-root") as HTMLElement;
// assuming in your html file has a div with id 'modal-root';

export class Modal extends React.Component<{ children?: React.ReactNode }> {
el: HTMLElement = document.createElement("div");

componentDidMount() {
modalRoot.appendChild(this.el);
}

componentWillUnmount() {
modalRoot.removeChild(this.el);
}

render() {
return ReactDOM.createPortal(this.props.children, this.el);
}
}

View in the TypeScript Playground

Using hooks

Same as above but using hooks

import { useEffect, useRef, ReactNode } from "react";
import { createPortal } from "react-dom";

const modalRoot = document.querySelector("#modal-root") as HTMLElement;

type ModalProps = {
children: ReactNode;
};

function Modal({ children }: ModalProps) {
// create div element only once using ref
const elRef = useRef<HTMLDivElement | null>(null);
if (!elRef.current) elRef.current = document.createElement("div");

useEffect(() => {
const el = elRef.current!; // non-null assertion because it will never be null
modalRoot.appendChild(el);
return () => {
modalRoot.removeChild(el);
};
}, []);

return createPortal(children, elRef.current);
}

View in the TypeScript Playground

Modal Component Usage Example:

import { useState } from "react";

function App() {
const [showModal, setShowModal] = useState(false);

return (
<div>
// you can also put this in your static html file
<div id="modal-root"></div>
{showModal && (
<Modal>
<div
style={{
display: "grid",
placeItems: "center",
height: "100vh",
width: "100vh",
background: "rgba(0,0,0,0.1)",
zIndex: 99,
}}
>
I'm a modal!{" "}
<button
style={{ background: "papyawhip" }}
onClick={() => setShowModal(false)}
>
close
</button>
</div>
</Modal>
)}
<button onClick={() => setShowModal(true)}>show Modal</button>
// rest of your app
</div>
);
}
Context of Example

This example is based on the Event Bubbling Through Portal example of React docs.