How to Use ReactDOM.createPortal() in React 16 (Even Without Official Documentation)

React has revolutionized how we build user interfaces, but every so often, we encounter edge cases where the standard component model feels limiting. One such case is rendering UI elements that need to "break out" of their parent component’s DOM hierarchy—think modals, tooltips, or notifications that should overlay the entire page, not affected by parent CSS constraints like overflow: hidden or z-index stacking contexts.

Enter React Portals, introduced in React 16. Portals let you render children into a DOM node outside the parent component’s DOM tree while keeping them fully integrated into React’s component lifecycle, props, and context. Even if you’re navigating without official documentation (or just want a deeper dive), this guide will walk you through everything you need to know to master portals.

Table of Contents#

  1. What Are React Portals?
  2. Syntax and Core Concepts
  3. Step-by-Step Implementation Guide
  4. Common Use Cases for Portals
  5. Event Bubbling with Portals
  6. Common Pitfalls and How to Avoid Them
  7. Server-Side Rendering (SSR) Considerations
  8. Conclusion
  9. References

What Are React Portals?#

At their core, portals are a way to render React components into a DOM node that exists outside the parent component’s DOM hierarchy. Normally, when you render a component, React appends it to the DOM tree of its parent, creating a nested structure. Portals bypass this by rendering directly into a separate DOM node (e.g., a <div> at the bottom of <body>), but they remain fully part of React’s component tree.

Key Properties:#

  • React Tree Integration: Portals behave like regular components—they receive props, access context, and participate in state updates.
  • DOM Independence: Rendered into a separate DOM node, avoiding parent CSS constraints (e.g., overflow: hidden, z-index limits).
  • Event Bubbling: Events from portals bubble up to React parent components, even if they’re in a different DOM tree.

Syntax and Core Concepts#

The portal API is surprisingly simple. The primary method is ReactDOM.createPortal(), which takes two arguments:

ReactDOM.createPortal(child, container);  

Parameters:#

  • child: The content to render (can be a React element, string, fragment, or even another component).
  • container: The DOM node where the child will be rendered (e.g., document.getElementById('portal-root')).

How It Works:#

  1. You define a "portal container" in your HTML (e.g., a <div id="portal-root"> alongside your main React root).
  2. In your component, call createPortal() with the content to render and the portal container.
  3. React renders the content into the portal container but keeps it linked to the parent component’s state and lifecycle.

Step-by-Step Implementation Guide#

Let’s build a practical example: a modal dialog that uses a portal to render outside the main React root. This avoids issues where the modal might be clipped by a parent with overflow: hidden.

Step 1: Set Up the Portal Container#

First, add a dedicated DOM node for portals in your public/index.html (or equivalent HTML entry point). This sits alongside your main React root (<div id="root">):

<!-- public/index.html -->  
<body>  
  <div id="root"></div>  
  <!-- Portal container -->  
  <div id="portal-root"></div>  
</body>  

Step 2: Create a Portal Component#

Next, build a reusable Portal component that wraps createPortal(). This keeps your code clean and reusable across multiple portal use cases:

// src/components/Portal.js  
import React from 'react';  
import ReactDOM from 'react-dom';  
 
const Portal = ({ children }) => {  
  // Find the portal container in the DOM  
  const portalContainer = document.getElementById('portal-root');  
 
  // Fallback: create the container if it doesn't exist (for SSR or dynamic setups)  
  if (!portalContainer) {  
    const div = document.createElement('div');  
    div.id = 'portal-root';  
    document.body.appendChild(div);  
    portalContainer = div;  
  }  
 
  // Render children into the portal container  
  return ReactDOM.createPortal(children, portalContainer);  
};  
 
export default Portal;  

Step 3: Build a Modal with the Portal#

Now, use the Portal component to create a modal. The modal will render into portal-root but still interact with its parent component’s state.

// src/components/Modal.js  
import React from 'react';  
import Portal from './Portal';  
 
const Modal = ({ isOpen, onClose, children }) => {  
  if (!isOpen) return null;  
 
  return (  
    <Portal>  
      {/* Semi-transparent overlay */}  
      <div style={overlayStyle} onClick={onClose}>  
        {/* Modal content (stops event propagation to overlay) */}  
        <div style={modalStyle} onClick={(e) => e.stopPropagation()}>  
          <button onClick={onClose} style={closeButtonStyle}>×</button>  
          {children}  
        </div>  
      </div>  
    </Portal>  
  );  
};  
 
// Basic styling (customize as needed)  
const overlayStyle = {  
  position: 'fixed',  
  top: 0,  
  left: 0,  
  right: 0,  
  bottom: 0,  
  backgroundColor: 'rgba(0, 0, 0, 0.5)',  
  display: 'flex',  
  alignItems: 'center',  
  justifyContent: 'center',  
};  
 
const modalStyle = {  
  backgroundColor: 'white',  
  padding: '2rem',  
  borderRadius: '8px',  
  minWidth: '300px',  
};  
 
const closeButtonStyle = {  
  position: 'absolute',  
  top: '1rem',  
  right: '1rem',  
  background: 'none',  
  border: 'none',  
  fontSize: '1.5rem',  
  cursor: 'pointer',  
};  
 
export default Modal;  

Step 4: Use the Modal in a Parent Component#

Finally, use the Modal component in a parent component to trigger it with a button:

// src/App.js  
import React, { useState } from 'react';  
import Modal from './components/Modal';  
 
function App() {  
  const [isModalOpen, setIsModalOpen] = useState(false);  
 
  return (  
    <div style={{ padding: '2rem' }}>  
      <h1>React Portal Demo</h1>  
      <button onClick={() => setIsModalOpen(true)}>Open Modal</button>  
 
      <Modal  
        isOpen={isModalOpen}  
        onClose={() => setIsModalOpen(false)}  
      >  
        <h2>Hello from the Portal!</h2>  
        <p>This modal is rendered outside the main React root.</p>  
      </Modal>  
    </div>  
  );  
}  
 
export default App;  

Result:#

Clicking "Open Modal" renders the modal into portal-root, overlaying the entire page. Closing it works via the parent component’s setIsModalOpen state—proof that portals remain tightly integrated with React’s state management.

Common Use Cases for Portals#

Portals shine in scenarios where UI elements need to escape their parent’s DOM constraints. Here are the most common use cases:

1. Modals and Dialogs#

As shown earlier, modals often need to overlay the entire viewport. Portals prevent them from being clipped by parent components with overflow: hidden or position: relative.

2. Tooltips and Dropdowns#

Tooltips or dropdowns that should appear above other content (e.g., a menu that extends beyond a parent container) benefit from portals to avoid being cut off.

3. Notifications and Toasts#

Global notifications (e.g., "File saved!") often render in a fixed position (top-right corner). Portals ensure they’re not constrained by parent layout.

4. Embedded Components in Third-Party Sites#

If you’re embedding a React component into a non-React app (e.g., a widget), portals let you render it into a specific DOM node provided by the host page.

Event Bubbling with Portals#

A critical (and often misunderstood) behavior of portals is event bubbling. Even though portals render into a separate DOM tree, their events bubble up to React parent components in the React component tree, not the DOM tree.

Example: Event Bubbling from Portal to Parent#

Suppose you have a portal inside a parent component, and the portal contains a button. Clicking the button will bubble the onClick event up to the parent component in React, even if the portal is in a different DOM branch.

// ParentComponent.js  
const ParentComponent = () => {  
  const handleClick = () => {  
    console.log("Event bubbled up to ParentComponent!");  
  };  
 
  return (  
    <div onClick={handleClick} style={{ border: '2px solid blue', padding: '1rem' }}>  
      <h3>Parent Component</h3>  
      <Portal>  
        <button>Click Me (In Portal)</button>  
      </Portal>  
    </div>  
  );  
};  

What happens? Clicking the portal button triggers handleClick in ParentComponent because the portal is part of the React component tree, even though it’s in a different DOM node.

Common Pitfalls and How to Avoid Them#

1. Forgetting to Create the Portal Container#

If the portal-root div doesn’t exist in your HTML, document.getElementById('portal-root') returns null, and createPortal will throw an error: Target container is not a DOM element.

Fix: Always define the portal container in your HTML, or dynamically create it if it’s missing (as shown in the Portal component earlier).

2. Overusing Portals#

Portals solve specific problems (DOM hierarchy constraints). Don’t use them for regular component rendering—stick to standard React rendering unless you need to escape the parent DOM.

3. Ignoring CSS for Portal Content#

Portal content inherits styles from its DOM container, not the React parent. If your portal content looks broken, check if it’s missing global styles (e.g., from CSS reset files).

4. Memory Leaks#

React automatically cleans up portal content when the component unmounts, so memory leaks are rare. However, if you dynamically create the portal container (e.g., in useEffect), ensure you remove it on unmount:

useEffect(() => {  
  const portalContainer = document.createElement('div');  
  document.body.appendChild(portalContainer);  
 
  return () => {  
    document.body.removeChild(portalContainer); // Cleanup  
  };  
}, []);  

Server-Side Rendering (SSR) Considerations#

Portals can be tricky with SSR (e.g., Next.js, Gatsby) because the portal container may not exist on the server during initial render. Here’s how to handle it:

1. Check for window Availability#

In SSR, window is undefined on the server. Use a conditional to render portals only on the client:

const Portal = ({ children }) => {  
  if (typeof window === 'undefined') return null; // Skip on server  
 
  const portalContainer = document.getElementById('portal-root');  
  return ReactDOM.createPortal(children, portalContainer);  
};  

2. Use Framework-Specific Solutions#

Frameworks like Next.js let you define portal containers in _document.js to ensure they exist during SSR:

// pages/_document.js (Next.js)  
import Document, { Html, Head, Main, NextScript } from 'next/document';  
 
class MyDocument extends Document {  
  render() {  
    return (  
      <Html>  
        <Head />  
        <body>  
          <Main />  
          <NextScript />  
          <div id="portal-root" /> {/* Portal container */}  
        </body>  
      </Html>  
    );  
  }  
}  
 
export default MyDocument;  

Conclusion#

ReactDOM.createPortal() is a powerful tool for rendering UI outside the parent DOM hierarchy while maintaining React’s component model. By mastering portals, you can build modals, tooltips, and other overlay elements that work reliably across complex layouts.

Remember: Portals are part of the React component tree, so props, context, and event bubbling work as expected. Use them sparingly for cases where DOM hierarchy constraints are a problem, and always ensure your portal container exists in the HTML (or is created dynamically).

References#