In V16 React, there was a new feature called Portals. The first time I saw the Portals feature, I didn’t see anything special about them. But while working on a recent business requirement, I realized how interesting Portals can be.

Scene: the repetition

Let’s restore the product requirements first.

Demand analysis

In this functional module, component A controls the display of the first level tabs, component B controls the display of the second level tabs, and component C is responsible for displaying the current active TAB content. Click on an item in the title list in component C (as shown on the left) to expand list detail D in the right image in this module. And the detail D will be displayed in this module with full width and height.

Implementation approach

The general structure of component C is simulated with the following code.


class C extends React.Component {
    constructor(props) {
        super(props)
        this.state = { visible: false }
    }
    handleClick = (a)= > {
        this.setState({ visible: false })
    }

    render() {
        return (
            <div>
                <Others onClick={this.handleClick} />
                {this.state.visible && <D />}
            </div>)}}Copy the code

Change the visible Boolean value in state in C by clicking on a title in Othors (mock code, forget the full implementation) to determine whether D is visible or not.

Since the requirement is to display 100% of the width and height, I am happy to write the CSS style for component D as follows:

.d {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    z-index: 10;
}
Copy the code

After saving CMD + S, go back to the browser to see the effect, and find that the requirements are indeed implemented, and the interaction is consistent with the design draft.

It doesn’t seem to make any sense what I said earlier. But as I drank a cold bottle of Fat Boy Happy Water, I realized that such code must be buggy.

At the scene of the bug

In the CSS code above, the first line position: Absolute is the problem.

We all know that the element applying position: absolute is offset with respect to the nearest non-static positioned ancestor element. In this case, the closest ancestor of the D component is C. Although CSS such as position: relative is not currently used in C component, if we need to use position: relative in C component for element positioning someday, the width and height of D component will only be sufficient for C component. (As shown below)

As a front-end, 100% restore UI is the dignity of front-end ER. It’s not like we can go to the product and say, “You can’t add location elements to the C component in the future, otherwise it will affect the functionality.”

So let’s abstract it now. In this requirement, we want to send COMPONENT D below component A when clicking on A title in component C, and then use position: Absolute to make component D overpower component A.

It sounds like we need A portal, and when component D comes out through this portal, it reaches component A.

React The World’s Portals

Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

Portals provide a great way to render child nodes to DOM nodes other than the parent component

Portals, Portals, Portals, Portals, Portals, Portals, Portals, Portals, Portals, Portals, Portals, Portals, Portals.

What is the Portals

Portals:

ReactDOM.createPortal(child, container)
Copy the code

The first argument is a renderable React child element, and the second argument is a DOM element.

Code transformation

Now let’s use Portals to transform our code.

Document.getelementbyid (‘component-a’); document.getelementById (‘component-a’);

<div id="component-a"> {/ *... Code for component A */} </div>Copy the code

Add position: relative to the CSS of component A to ensure that component D is absolutely located with component A.

Then create a component that applies Portals:

import * as React from 'react'
import { createPortal } from 'react-dom'

import './index.scss'
import { ComponentExt } from '@utils/reactExt'

export interface PortalsContainerProps {}

class PortalsContainer extends ComponentExt<PortalsContainerProps> {
    el: HTMLDivElement = null
    constructor(props: PortalsContainerProps) {
        super(props)
        const containers = document.getElementById('component-a')
        this.el = document.createElement('div')
        containers.appendChild(this.el)
    }
    componentWillUnmount() {
        document.getElementById('component-a').removeChild(this.el)
    }
    render() {
        return createPortal(<div className="portals-container">{this.props.children}</div>.this.el)
    }
}
export default PortalsContainer
Copy the code

The CSS part

.portals-container {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    z-index: 10;
}
Copy the code

Pass the D component as Children of the PortalsContainer:

import PortalsContainer from './PortalsContainer'. <PortalsContainer> {/* ... Code for component D */} </PortalsContainer>Copy the code

Now we can look at the DOM structure of the code optimized through the portal:

And then we find something really amazing. Component D is clearly A child of component C, but its DOM structure is now inserted directly under component A via Portals. Is it like React Portals opening up A portal for us, allowing our component D to pass directly into component A’s DOM structure?

In this way, our component D is positioned directly relative to the entire module component A, regardless of whether or not component C later adds A positioning element.

divergent

As I looked at Portals, I found that common UI components such as Modal pop-ups, global messages, and tootips could apply this feature. Relic of great!

For example, in Ant-Design’s Popover bubble card component, there is an application to Portals.

React/Popover/Popover/React/Popover/React

The arrow shows the use of Portals in Trigger. The Content component in the middle is where the Content in our card actually resides.

Ps: If you are interested in the component of this popbox class, you are highly recommended to check out the source code of rC-Trigger.

conclusion

React’s support for Portals is a great way to solve my business problems without having to consider some very hack methods. So write a blog post to describe such a process.

Looking back on the process from the first time I saw Portals to the later in-depth practice, I felt that I had to explore the boundary conditions of business scenarios more often. You may even gain some useful knowledge. You can’t just write buggy code to meet requirements and meet deadlines without thinking about it.