Ivan Kaminskyi

Apr 26, 202310 min

Mastering the Art of React Portals: Advanced Techniques for Powerful Web Applications

React portals provide a way to render children components outside the DOM hierarchy of their parent components. In other words, they allow us to render a component's children into a different part of the DOM than where the component itself is located. This can be very useful in situations where we need to render a component's children into a different part of the DOM, such as in a modal or a tooltip.

In this article, we'll take a look at what React portals are, how to use them, and some practical examples of how they can be used in web development.

What are React portals?

React portals were introduced in React version 16. They provide a way to render children components outside the DOM hierarchy of their parent components.

Before portals, if we wanted to render a modal, for example, we would need to create a top-level DOM element, append it to the document body, and render our modal component inside that element. This approach, however, had some limitations. For example, it could cause problems with z-indexes and other CSS styles.

React portals provide a better way to render components outside their parent components. With portals, we can render a component's children into a different part of the DOM hierarchy, without causing any issues with styles or other CSS properties.


How to use React portals

Using a portal in React is very simple. To create a portal, we need to use the ReactDOM.createPortal() method, which takes two arguments: the first argument is the content we want to render, and the second argument is the DOM element where we want to render the content.

Here's an example of how to create a portal:

import React from 'react';
import ReactDOM from 'react-dom';

const MyPortal = ({ children }) => {
  const portalElement = document.getElementById('portal-root');

  return ReactDOM.createPortal(
    children,
    portalElement,
  );
};

export default MyPortal;

In this example, we've created a component called MyPortal that takes a children prop. Inside the component, we're using ReactDOM.createPortal() to render the children into an element with the ID portal-root.

Now, let's take a look at how we can use this portal in our app.

import React from 'react';
import MyPortal from './MyPortal';

const App = () => {
  return (
    <div>
      <h1>Hello World!</h1>
      <MyPortal>
        <h2>This is a portal</h2>
      </MyPortal>
    </div>
  );
};

export default App;

In this example, we're rendering a MyPortal component inside an App component. The MyPortal component contains an h2 element, which will be rendered inside the portal-root element.

Deep Dive into Portal Mechanics

React Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. This section explores the mechanics of portals, emphasizing lifecycle management and integration with the rest of your application.

Lifecycle Events in Portals

Portals handle lifecycle events like normal React child elements. Even if a portal involves rendering in a different part of the DOM tree, it still behaves like a standard React child on mounting, updating, and unmounting.

Event Bubbling with Portals

An important aspect of portals is their event bubbling behavior. Events fired from inside a portal will bubble up through the React component tree, not the DOM tree, allowing for consistent integration with React's event system:

<div onClick={handleParentClick}>
  <Modal>
    <button onClick={handleModalClick}>Click</button>
  </Modal>
</div>

Here, a click on the button in the Modal will bubble up to the parent div, despite being in a different part of the DOM.


Practical examples of using React portals

Now that we know how to create and use portals in React, let's take a look at some practical examples of how we can use them in web development.

Modals

One of the most common use cases for portals is to create modals. With portals, we can render a modal outside the main app hierarchy, which makes it easier to style and manage.

Here's an example of how to create a modal using portals:

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./Modal.css";

const Modal = ({ onClose, children }) => {
  const [isOpen, setIsOpen] = useState(true);

  const handleClose = () => {
    setIsOpen(false);
    onClose();
  };

  if (!isOpen) {
    return null;
  }

  return ReactDOM.createPortal(
    <div className="modal-overlay">
      <div className="modal">
        <button onClick={handleClose} className="close-button">
          X
        </button>
        {children}
      </div>
    </div>,
    document.getElementById("modal-root")
  );
};

export default Modal;

In this example, we've created a Modal component that takes an onClose prop and a children prop. We're using state to keep track of whether the modal is open or closed, and we're rendering the modal using ReactDOM.createPortal().

Tooltips

Another common use case for portals is to create tooltips. Tooltips are small pop-up windows that appear when the user hovers over an element.

Here's an example of how to create a tooltip using portals:

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import './Tooltip.css';

const Tooltip = ({ text, children }) => {
  const [isOpen, setIsOpen] = useState(false);

  const handleMouseEnter = () => {
    setIsOpen(true);
  };

  const handleMouseLeave = () => {
    setIsOpen(false);
  };

  return (
    <div className="tooltip-container"
         onMouseEnter={handleMouseEnter}
         onMouseLeave={handleMouseLeave}>
      {children}
      {isOpen && ReactDOM.createPortal(
        <div className="tooltip">
          {text}
        </div>,
        document.getElementById('tooltip-root'),
      )}
    </div>
  );
};

export default Tooltip;

In this example, we've created a Tooltip component that takes a text prop and a children prop. We're using state to keep track of whether the tooltip is open or closed, and we're rendering the tooltip using ReactDOM.createPortal().

Popovers

Popovers are UI components that display additional information when a user interacts with a specific element on a page, such as a button or an icon. They are typically used to provide contextual information or actions related to the element.

Here's an example of how to create a popover using portals:

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import './Popover.css';

const Popover = ({ content, children }) => {
  const [isOpen, setIsOpen] = useState(false);

  const handleMouseEnter = () => {
    setIsOpen(true);
  };

  const handleMouseLeave = () => {
    setIsOpen(false);
  };

  return (
    <div className="popover-container">
      <div
        className="popover-trigger"
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      >
        {children}
      </div>
      {isOpen && ReactDOM.createPortal(
        <div className="popover-content">{content}</div>,
        document.getElementById('popover-root'),
      )}
    </div>
  );
};

export default Popover;

In this example, we've created a Popover component that takes a content prop and a children prop. We're using state to keep track of whether the popover is open or closed, and we're rendering the popover using ReactDOM.createPortal().

Drag and Drop

React portals can also be used to implement drag and drop functionality. Drag and drop is a user interface technique that allows users to click and drag an element to a new location, typically used for reordering items or moving files.

Here's an example of how to implement drag and drop using portals:

import React, { useState, useRef } from 'react';
import ReactDOM from 'react-dom';
import './Draggable.css';

const Draggable = ({ children }) => {
  const [isDragging, setIsDragging] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const ref = useRef(null);

  const handleMouseDown = (event) => {
    setIsDragging(true);
    setPosition({
      x: event.clientX - ref.current.offsetLeft,
      y: event.clientY - ref.current.offsetTop,
    });
  };

  const handleMouseMove = (event) => {
    if (!isDragging) return;
    setPosition({
      x: event.clientX - ref.current.offsetWidth / 2,
      y: event.clientY - ref.current.offsetHeight / 2,
    });
  };

  const handleMouseUp = () => {
    setIsDragging(false);
  };

  return (
    <>
      <div
        ref={ref}
        className="draggable"
        style={{ left: `${position.x}px`, top: `${position.y}px` }}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUp}
      >
        {children}
      </div>
      {ReactDOM.createPortal(
        <div className={`draggable-overlay ${isDragging ? 'active' : ''}`} />,
        document.body,
      )}
    </>
  );
};

export default Draggable;

In this example, we've created a Draggable component that takes a children prop. We're using state to keep track of whether the draggable element is being dragged, and we're rendering the draggable element and an overlay using ReactDOM.createPortal(). The overlay is used to prevent other elements on the page from being interacted with while the element is being dragged.

Video Player

React portals can also be used to create custom video players. Video players are UI components that allow users to play, pause, and interact with video content.

Here's an example of how to create a custom video player using portals:

import React, { useState, useRef } from 'react';
import ReactDOM from 'react-dom';
import './VideoPlayer.css';

const VideoPlayer = ({ src, poster }) => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const videoRef = useRef(null);

  const handlePlay = () => {
    setIsPlaying(true);
    videoRef.current.play();
  };

  const handlePause = () => {
    setIsPlaying(false);
    videoRef.current.pause();
  };

  const handleTimeUpdate = () => {
    setCurrentTime(videoRef.current.currentTime);
    setDuration(videoRef.current.duration);
  };

  return (
    <div className="video-player">
      <video
        ref={videoRef}
        src={src}
        poster={poster}
        onPlay={handlePlay}
        onPause={handlePause}
        onTimeUpdate={handleTimeUpdate}
      />
      {ReactDOM.createPortal(
        <div className="video-controls">
          <button onClick={isPlaying ? handlePause : handlePlay}>
            {isPlaying ? 'Pause' : 'Play'}
          </button>
          <div className="video-progress">
            <div className="video-progress-bar" style={{ width: `${(currentTime / duration) * 100}%` }} />
          </div>
        </div>,
        document.body,
      )}
    </div>
  );
};

export default VideoPlayer;

In this example, we've created a VideoPlayer component that takes a src prop for the video URL and a poster prop for the video poster image. We're using state to keep track of whether the video is playing, the current time, and the duration. We're rendering the video element and the video controls using ReactDOM.createPortal().


Troubleshooting Common Issues with React Portals

1. Portal Container Not Found

Issue: The DOM node designated for the portal (modal-root in most examples) isn't found.

Solution: Ensure that the element exists in your HTML before your React component mounts. This can be verified by inspecting the initial HTML output or dynamically creating the node during the React component's initialization phase.

2. Memory Leaks

Issue: Components rendered in portals can sometimes cause memory leaks if they are not properly cleaned up.

Solution: Make sure to unmount any event listeners or intervals set up in the portal component when the component unmounts.

3. Styling Conflicts

Issue: Styles leak into components rendered in the portal or vice versa due to CSS specificity.

Solution: Use more specific CSS selectors or isolate the portal's styles by using CSS modules or styled-components to avoid clashes.

4. Testing Components Using Portals

Issue: Components that render portals might fail tests due to the portal's detached DOM tree.

Solution: Update your testing setup to mock or substitute portal behavior, or ensure your tests account for the portal environment by using libraries like @testing-library/react that handle this setup.

5. Event Propagation

Issue: Unexpected behavior in event handling due to propagation from portal components.

Solution: Explicitly manage event bubbling and capturing within portal components to prevent unexpected interactions with the rest of your app.

By understanding and preparing for these common pitfalls, you can effectively use React Portals in your projects while minimizing disruptions and bugs.


Conclusion

React portals provide a way to render children components outside the DOM hierarchy of their parent components. They can be very useful in situations where we need to render a component's children into a different part of the DOM, such as in a modal or a tooltip.

In this article, we've looked at what React portals are, how to use them, and some practical examples of how they can be used in web development. With this knowledge, you should be able to use React portals to improve the user experience of your web applications.

Tags:
React
Share:

Related Posts

Get The Latest Insights straight To Your Inbox For Free!

Subscribe To The NewsletterSubscribe To The NewsletterSubscribe To The Newsletter