Learn React Portals by example
Tags: react, javascript, webdev
Published: 2020-01-02 14:07:12

---
title: Learn React Portals by example
published: true
date: 2019-12-23 14:00:08 UTC
tags: react,javascript,webdev
canonical_url: https://blog.logrocket.com/learn-react-portals-by-example/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ms70yfpnrs3ft9ww2y0f.png
---
**Written by [Alexander Solovyev](https://blog.logrocket.com/author/supromikali/)**✏️

In this article, we’re going to take a look at a real world application for React Portals and explain how it can be helpful for solving the `overflow:hidden` problem on a tooltip example.

This is a very common problem that arises all the time in web development: you want to build some tooltip or dropdown, but it’s cut by the parent element `overflow: hidden` styling:

![Click me](https://blog.logrocket.com/wp-content/uploads/2019/12/click-me-nocdn.png)

In the screenshot above, the parent container with the `overflow:hidden` style is marked with red and the element that is used for positioning is marked with green.

### CSS/HTML solution (with downsides)

The simplest way to solve this issue is by simply removing the overflow styling:

![no padding](https://blog.logrocket.com/wp-content/uploads/2019/12/padding-nocdn.png)

The tooltip is now fully visible and everything looks good, but it becomes a very fragile solution when any of the following scenarios arise:

1. Somebody could accidentally add `overflow: hidden` to the parent again (and forget to click your button with tooltip for testing!)
2. Somebody could add another parent wrapper around it, for example, to introduce some extra styling in some cases.
3. There is also the possibility that `overflow: hidden` was there for a reason, for example, to crop an image.

Here’s an example of an unwanted side effect of disabling `overflow: hidden`:  
Before (image is inside the bounds of the card):

![parent company styling](https://blog.logrocket.com/wp-content/uploads/2019/12/click-me-styling-of-the-parent-container-nocdn.png)

After (image has expanded far outside of the card marked with green):

![green square](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/green-square-no-cdn.png?resize=730%2C316&ssl=1)

### React Portal in action

There’s a way to solve all the problems with tooltip/dropdown cut off by overflow for the entire application and reuse the code without needing to spend developer time on trying and testing.

The solution is to append tooltip or dropdown directly to the body of the document, set position: fixed style, and provide screenX and screenY coordinates where the tooltip/dropdown should appear.

Now, there are two things we need to do:

1. Append the tooltip/dropdown to the body of the document outside of the  React mount root
2. Take coordinates for placing the tooltip/dropdown (for example, using `useRef` React hook)

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

Let’s start with mounting outside of React. That’s an easy task for a JQuery/Vanilla JS codebase, but it might sound challenging to a React developer because React applications usually have only one mount point to the DOM. For example, some div with `id = "root"`.

Luckily, the React team introduced an additional way to mount components: React Portal.

Using React Portal, developers can access the tooltip/dropdown component from JSX in a convenient way: all of the props pass and handle events, but at the same time Portal is mounted to the body of the document outside of the React mount root.

The final JSX we are going to use is as follows:

```jsx
    <Portal>
       <TooltipPopover coords={coords}>
          Awesome content that will never be cut off again!
       </TooltipPopover>
    </Portal>
```

In the code snippet above, the `<Portal />` wrapper component takes care of mounting outside of React and `<TooltipPopover/>` is placed according to the coordinates passed to it. The final look is as follows:

![content not cut off](https://blog.logrocket.com/wp-content/uploads/2019/12/content-not-cut-off-nocdn.png)

That’s it: a universal solution for any content that should pop up outside of the parent without being cut off. But the `<Portal/>` wrapper component is a “black box” for us, so let’s change that and look at what’s under the hood.

### Building a Portal wrapper

By following [React docs for Portal](https://reactjs.org/docs/portals.html) we can build our own custom `<Portal/>` wrapper component from scratch in a few steps:

**Step 1: Adding an extra mount point in a DOM outside of “react-root”**

```jsx
<html>
    <body>
        <div id="react-root"></div> // [ 1 ]
        <div id="portal-root"></div>
    </body>
</html>
```

In this code snippet, I have named the React mount point element id `"react-root"`, and all of the tooltips/dropdowns should be mounted using React Portal inside of `"portal-root"`.

**Step 2: Build a reusable Portal wrapper component using `createPortal` in React**

Here is a simplified `<Portal/>` wrapper component code written with React Hooks:

```jsx
import { useEffect } from "react";
import { createPortal } from "react-dom";

const Portal = ({children}) => {
  const mount = document.getElementById("portal-root");
  const el = document.createElement("div");

  useEffect(() => {
    mount.appendChild(el);
    return () => mount.removeChild(el);
  }, [el, mount]);

  return createPortal(children, el)
};

export default Portal;
```

As you can see, mount needs a DOM element with `id = "portal-root"` from the previous code snippet with HTML to append an element inside. The core thing this wrapper component does is create a Portal for any React children passed into a component.

The `useEffect` React Hook is used here to take care of mounting the element at the right time and to clean up on component unmount.

**Step 3: Passing button coordinates to the tooltip for positioning using React Hooks**

The last thing we need to do to get the fully-functional tooltip component is pass button coordinates to the tooltip for positioning. That is not a hard task thanks to React Hooks, and it can be implemented with something like this:

```jsx
const App = () => {
  const [coords, setCoords] = useState({}); // takes current button coordinates
  const [isOn, setOn] = useState(false); // toggles button visibility

  return <Card style={{...styles.card, overflow: "hidden"}}> // [ 2 ]
      <Button
        onClick={e => {
          const rect = e.target.getBoundingClientRect();
          setCoords({
            left: rect.x + rect.width / 2,
            top: rect.y + window.scrollY
          });
          setOn(!isOn); // [ 3 ]
        }}
      >
        Click me
      </Button>
      {
        isOn &&
        <Portal>
          <TooltipPopover coords={coords}>
            <div>Awesome content that is never cut off by its parent container!</div>
          </TooltipPopover>
        </Portal>
      }
  </Card>
}
```

In this code, the button component has an `onClick` event handler that takes current onscreen coordinates of the button from an e.target object using the standard [getBoundingClientRect() method](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of a DOM element.

Additionally, a toggler for button visibility is in place that helps us to toggle the tooltip.

Please note that I left `overflow: hidden` intentionally on the Card component to showcase that the Portal solution is working fine.

Feel free to check the live demo and full code on [codesandbox.](https://codesandbox.io/s/github/supromikali/react-portals-example)

**Bonus: prevent tooltips from “jumps” on page content change**

There is one thing that refers to the tooltips positioning more than to Portals, but it’s worth mentioning: incase the button position depends on the right edge of the window (for example, `display: flex; margin-left: auto` styling), its positioning could be affected by the window scroll appearing (for example, when new content is loaded at the bottom of the page).

Let’s take a look at an example:

Before: window has no scroll and the tooltip is centered relative to the button.

![awesome content](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/awesome-content.png?resize=730%2C250&ssl=1)

After: window scroll has appeared, and the tooltip is a bit off center (exactly the same amount of pixels as the scroll added).

![out of card](https://blog.logrocket.com/wp-content/uploads/2019/12/out-of-card-nocdn.png)

There are a few ways to solve this issue. You could use some resize detection package applied to the whole page like `react-resize-detector`, which will fire some event on content height change.

Then, we can measure the scroll width and correct the position of the tooltip.

Luckily, in our case, there is a much simpler pure CSS solution:

```jsx
html {
    overflow-x: hidden;
    width: 100vw;
}
```

Adding this little code snippet to the page prevents the content of the page from unexpected “jumps” on window scroll appear/hide because the `<html/>` width is set to be equal to `100vw` (window width), which is constant and unaffected by the window scroll.

Meanwhile, the `100% <html/>` width doesn’t include the scroll, so the app doesn’t care anymore about the scroll being on or off. Tooltip will be centered all the time.

You can test the result on the demo [https://xshnz.csb.app/](https://xshnz.csb.app/) by playing with window height size.

Doing the same thing but with better-looking cross-browser scrollbars is also possible using a package called `react-custom-scrollbars`.

To make it work, you basically need to install the package and wrap the whole app into a Scrollbars component like this:

```jsx
import { Scrollbars } from 'react-custom-scrollbars';

ReactDOM.render(
  <Scrollbars style={{ width: "100vw", height: "100vh" }}>
    <App />
  </Scrollbars>, 
  document.getElementById("react-root")
);
```

Here is a quick preview (note the scrollbar appearance):

![awesome content](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/awesome-content.png?resize=730%2C250&ssl=1)

### Conclusion

We have gone through the most common use case for React Portal step by step, explaining how it works on a real-life example with tooltip component development from scratch.

Of course, generalization can’t come without its tradeoffs. The complexity of Portal tooltip is bigger than the pure CSS/HTML solution, and it’s up to the developer to choose the right approach at the right time.

* * *
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://logrocket.com/signup/)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/qrrlz9yuu7k9y897sobg.png)

[LogRocket](https://logrocket.com/signup/) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://logrocket.com/signup/)
* * *

The post [Learn React Portals by example](https://blog.logrocket.com/learn-react-portals-by-example/) appeared first on [LogRocket Blog](https://blog.logrocket.com).