Templates are very useful for getting the user’s attention quickly. They can be used to collect user information, provide updates, or encourage users to take action. A study of 2 billion pop-up ads showed that the top 10 percent had a conversion rate of more than 9 percent.

However, I think it’s safe to say that modal establishment requires some patience. Keeping track of all the Z-Index values, layers, and DOM hierarchies is not easy. This difficulty extends to other elements that need to be rendered at the top, such as overlays or tooltips.

In the React application, a component or element is loaded into the DOM as a child of the nearest parent node. From top to bottom, the hierarchy of standards is as follows. Root nodes => Parent Nodes => Child Nodes => Leaf Nodes.

If the parent node has an overflow hide attribute or has elements at higher levels, then the child node cannot appear at the top level and is restricted to the visible area of the parent node. We could try to set a very high Z-index value to bring the child nodes to the top level, but this strategy would be tedious and not necessarily successful.

This is where React Portals come in. React Portals provide an element with the ability to render outside the default hierarchy without affecting parent-child relationships between components.

In this article, we will demonstrate how to use React Portals to establish a mode in React. The methods used in this article can also be applied to building tooltips, full-page top-level sidebars, global search overalls, or dropdowns inside hidden overflow parent containers.

So, without further ado, let’s begin the magic……

Let’s start

Let’s first Create a new React App using the Create React App template or your own React App Settings.

# using yarn
yarn create react-app react-portal-overlay
# using npx
npx create-react-app react-portal-overlay

Copy the code

Next, switch to the app directory and start the React app.

# cd into app directory
cd react-portal-overlay
# start using yarn
yarn start
# start using npm
npm run start

Copy the code

Summary of the component

We will create two components and render them in the app components already available in the template.

But first, here are some important definitions.

  • ReactPortal: a wrapper component that creates a Portal and renders content in the supplied container outside of the default hierarchy
  • Modal: a basic modal component whose JSX content is to be usedReactPortal
  • App(Any component) : we will useModalComponent and keep it active (open or closed)

Create the React Portal

You can create a React Portal using createPortal in the React -dom. It takes two arguments.

  1. content: Any valid renderable React element
  2. containerElement: a valid DOM element to which we can attach.content
ReactDOM.createPortal(content, containerElement);
Copy the code

We will create a new component, reactportal.js, under the SRC/Components directory and add this fragment.

// src/components/ReactPortal.js
import { createPortal } from 'react-dom';

function ReactPortal({ children, wrapperId }) {
return createPortal(children, document.getElementById(wrapperId));
}
export default ReactPortal;

Copy the code

The ReactPortal component accepts the wrapperId attribute, which is the ID of a DOM element. We use this code to find an element with the ID provided and use it as a containerElement for the portal.

Note that the createPortal() function does not create containerElement for us. This function expects containerElement to already be available in the DOM. That’s why we had to add it ourselves in order for the portal to render content in this element.

We can customize the ReactPortal component and create an element with the ID provided if such an element is not found in the DOM.

First, we add an auxiliary function that creates an empty div with the given ID, appends it to the body, and returns the element.

function createWrapperAndAppendToBody(wrapperId) {
const wrapperElement = document.createElement('div');
wrapperElement.setAttribute("id", wrapperId);
document.body.appendChild(wrapperElement);
return wrapperElement;
}

Copy the code

Next, let’s update ReactPortal components to use createWrapperAndAppendToBody auxiliary method.

// Also, set a default value for wrapperId prop if none provided function ReactPortal({ children, wrapperId = "react-portal-wrapper" }) { let element = document.getElementById(wrapperId); // if element is not found with wrapperId, // create and append to body if (! element) { element = createWrapperAndAppendToBody(wrapperId); } return createPortal(children, element); }Copy the code

There is a limitation to this method. If the wrapperId property changes, the ReactPortal component will not be able to process the latest property value. To solve this problem, we need to move any logic that depends on wrapperId to another operation or side effect.

Dealing with a dynamicwrapperId

React HooksuseLayoutEffect and useEffect achieve similar results, but with slightly different usages. A quick rule of thumb is to use useLayoutEffect if the effects need to be synchronized and there are any direct mutations in the DOM. Since this is quite rare, useEffect is usually the best choice. UseEffect Runs asynchronously.

In this case, we mutated the DOM directly and wanted to run the effects synchronously before the DOM was redrawn, so it made more sense to use the useLayoutEffect Hook.

First, let’s move the lookup element and create logic to the useLayoutEffect Hook, with the wrapperId as the dependency. Next, we’ll set Element to the state. When the wrapperId changes, the component is updated accordingly.

import { useState, useLayoutEffect } from 'react'; / /... function ReactPortal({ children, wrapperId = "react-portal-wrapper" }) { const [wrapperElement, setWrapperElement] = useState(null); useLayoutEffect(() => { let element = document.getElementById(wrapperId); // if element is not found with wrapperId or wrapperId is not provided, // create and append to body if (! element) { element = createWrapperAndAppendToBody(wrapperId); } setWrapperElement(element); }, [wrapperId]); // wrapperElement state will be null on very first render. if (wrapperElement === null) return null; return createPortal(children, wrapperElement); }Copy the code

Now, we need to deal with the cleanup.

Treatment effect cleaning

We mutate the DOM directly and append an empty div to the body if no element is found. Therefore, we need to ensure that the empty divs that were dynamically added are removed from the DOM when the ReactPortal component is unloaded. Also, we must avoid removing any existing elements during the cleanup.

Let’s add a systemCreated sign, when createWrapperAndAppendToBody is invoked, it is set to true. If systemCreated is true, we remove the element from the DOM. The updated useLayoutEffect will look something like this.

/ /... useLayoutEffect(() => { let element = document.getElementById(wrapperId); let systemCreated = false; // if element is not found with wrapperId or wrapperId is not provided, // create and append to body if (! element) { systemCreated = true; element = createWrapperAndAppendToBody(wrapperId); } setWrapperElement(element); return () => { // delete the programatically created element if (systemCreated && element.parentNode) { element.parentNode.removeChild(element); } } }, [wrapperId]); / /...Copy the code

We have created the portal and customized it to be fail-safe. Next, let’s create a simple modal component and render it using React Portal.

Build a demo mode

To build the Modal component, we first create a new directory under SRC/Components: Modal, and add two new files: modal. js and modalstyles.css.

This modal component accepts several properties.

  • isOpen: A Boolean flag representing the state of the mode (on or off), controlled by the parent component rendering the mode.
  • handleClose: a method called by clicking the close button or any action that triggers the close.

Only when isOpen will the modal component render the content true. The false modal component will return null because we do not want it to remain in the DOM when we close the modal.

// src/components/Modal/Modal.js import "./modalStyles.css"; function Modal({ children, isOpen, handleClose }) { if (! isOpen) return null; return ( <div className="modal"> <button onClick={handleClose} className="close-btn"> Close </button> <div className="modal-content">{children}</div> </div> ); } export default Modal;Copy the code

Demonstrate modal styles

Now, let’s add some styles to the modes.

/* src/components/Modal/modalStyles.css */ .modal { position: fixed; inset: 0; /* inset sets all 4 values (top right bottom left) much like how we set padding, margin etc., */ background-color: Rgba (0, 0, 0, 0.6); display: flex; flex-direction: column; align-items: center; justify-content: center; The transition: all 0.3 s ease - in-out; overflow: hidden; z-index: 999; padding: 40px 20px 20px; } .modal-content { width: 70%; height: 70%; background-color: #282c34; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 2rem; }Copy the code

This code will make the mode occupy the entire viewport and align the. Modal-content center in both vertical and horizontal directions.

Use the escape key to close the mode

Modes can be closed by clicking Close, triggering handleClose. Let’s also add the ability to turn off modes by pressing an escape key. To do this, we attach the useEffect KeyDown event listener. We will remove the event listener during effect cleanup.

In a keystroke event, if the Escape key is pressed, we call handleClose.

// src/components/Modal/Modal.js import { useEffect } from "react"; import "./modalStyles.css"; function Modal({ children, isOpen, handleClose }) { useEffect(() => { const closeOnEscapeKey = e => e.key === "Escape" ? handleClose() : null; document.body.addEventListener("keydown", closeOnEscapeKey); return () => { document.body.removeEventListener("keydown", closeOnEscapeKey); }; }, [handleClose]); if (! isOpen) return null; return ( <div className="modal"> <button onClick={handleClose} className="close-btn"> Close </button> <div className="modal-content">{children}</div> </div> ); }; export default Modal;Copy the code

Our modal components are now ready to go

Get rid of the default DOM hierarchy

Let’s render the Modal component of the demo in an application.

To control the mode on and off behavior, we will initialize the state isOpen with useState Hook and set it to the default false. Next, we will add a button click, button onClick, set the isOpen state to true, and turn on the mode.

Now we will send isOpen and handleClose as properties to the Modal component. The handleClose property is just a callback method that sets the isOpen state to false to turn off the mode.

// src/App.js
import { useState } from "react";
import logo from "./logo.svg";
import Modal from "./components/Modal/Modal";
import "./App.css";

function App() {
const [isOpen, setIsOpen] = useState(false);

return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<button onClick={() => setIsOpen(true)}>
Click to Open Modal
</button>

<Modal handleClose={() => setIsOpen(false)} isOpen={isOpen}>
This is Modal Content!
</Modal>
</header>
</div>
);
}

export default App;

Copy the code

Modes can be turned on by clicking on the Click to Open Modal button. Modes can be turned off by pressing the escape key or clicking the Close button. Either operation triggers the handleClose method and closes the mode.

If we look at the DOM tree, we see that modal is rendered as a child node of the Header according to the default DOM hierarchy.

The built mode does not have a ReactPortal.

Let’s wrap the return JSX of the modes with ReactPortal so that the modes are rendered outside the DOM hierarchy and in the supplied container elements. A dynamic container is attached to the last child of the subject in the DOM.

The latest return method for the Modal component should look like this.

// src/components/Modal/Modal.js import ReactPortal from ".. /ReactPortal"; / /... function Modal({ children, isOpen, handleClose }) { // ... return ( <ReactPortal wrapperId="react-portal-modal-container"> <div className="modal"> // ... </div> </ReactPortal> ); } / /...Copy the code

Since we didn’t add a container with the React-portal-modal-Container ID, an empty div will be created with that ID, which will then be appended to the body. The Modal component will be rendered in this newly created container, outside of the default DOM hierarchy. Only the generated HTML and DOM trees are changed.

The parent-child relationship between the React component header and the Modal component remains the same.

A mode built with Replay Portal.

As you can see below, our demo rendered correctly, but its user interface turned on and off too instantly.

Modes constructed without CSSTransition.

Application of the transitionCSSTransition

To adjust the transition between open and closed modes, we can remove return NULL when closing the Modal component. We can control the visibility of modes using CSS, using the opacity and transform properties, and a conditionally added class, show/hide.

The Show /hide class can be used to set or reset visibility and animate on and off with transition properties. This works fine, except that the modes remain in the DOM even after they are closed.

We could also set the display property to None, but this would be the same as return null. Both of these attributes remove elements from the DOM immediately, without waiting for transitions or animations to complete. This is where the [CSSTransition] component comes in.

The transition is run by wrapping the element to be transitioned in the [CSSTransition] component and setting the unmountOnExit property to true, and once the transition is complete, the element is removed from the DOM.

First, we install the react-transition-group dependency.

# using yarn
yarn add react-transition-group
# using npm
npm install react-transition-group

Copy the code

Next, we import the CSSTransition component and use it to wrap everything under the ReactPortal in the Modal return JSX.

The trigger, duration, and style of this component can all be controlled by setting the CSSTransition property.

  • in: Boolean flag that triggers an enter or exit state
  • timeout: The transition time for each state (enter, exit, etc.).
  • unmountOnExit: Unmounts components after exiting
  • classNames: The class name for each state (enter, exit, etc.) is suffixed to control CSS customization
  • nodeRef: React reference to the DOM element that needs to be transformed (in this case, it isModalThe roots of the componentdivElements)

A ref can be created using the useRef Hook. This value is passed to the CSSTransition’snodeRef attribute. It is attached as a ref attribute to the root div of Modal’ to connect the CSSTransition component to the elements that need to be transitioned.

// src/components/Modal/Modal.js
import { useEffect, useRef } from "react";
import { CSSTransition } from "react-transition-group";
// ...

function Modal({ children, isOpen, handleClose }) {
const nodeRef = useRef(null);
// ...

// if (!isOpen) return null; <-- Make sure to remove this line.

return (
<ReactPortal wrapperId="react-portal-modal-container">
<CSSTransition
in={isOpen}
timeout={{ entry: 0, exit: 300 }}
unmountOnExit
classNames="modal"
nodeRef={nodeRef}
>
<div className="modal" ref={nodeRef}>
// ...
</div>
</CSSTransition>
<ReactPortal wrapperId="react-portal-modal-container">
);
}
// ....

Copy the code

Next, let’s add some transition styles to the state prefix classes added to the CSSTransition component, modal-Enter-done and modal-Exit.

.modal { ... opacity: 0; pointer-events: none; The transform: scale (0.4); } .modal-enter-done { opacity: 1; pointer-events: auto; transform: scale(1); } .modal-exit { opacity: 0; The transform: scale (0.4); }...Copy the code

Now, the user interface for the demo mode opens and closes more smoothly, without affecting DOM load.

Modes constructed with CSSTransition.

conclusion

In this article, we demonstrate the capabilities of React Portals using a React Portal modal example. However, the use of React Portals is not limited to mode or overlay. We can also use React Portals to render a component on top of everything in the wrapper layer.

By wrapping a component’s JSX or component itself with ReactPortal, we can bypass the default DOM hierarchical behavior and get the benefits of React Portals on any component.

import ReactPortal from "./path/to/ReactPortal"; function AnyComponent() { return ( <ReactPortal wrapperId="dedicated-container-id-if-any"> {/* compontents JSX to render  */} </ReactPortal> ); }Copy the code

That’s all for now! You can find the final components and styles of this article in the GitHub repo, and access the final [ReactPortal] and modal component actions here.

Thank you for reading. I hope you found this article helpful. Please share it with others, they may find it beneficial. Ciao!

The postBuilding a modal in React with React Portalsappeared first onLogRocket Blog.