📜 ⬆️ ⬇️

Portals in React.js

image


Probably every front-end developer had to do all sorts of pop-ups or tooltips. And almost always the moment comes when such a thing needs to be displayed inside an element with overflow: hidden . This is the moment in SmartProgress.


We at SmartProgress use React to develop interfaces and we really wanted to find a react-way solution. Portals are rushing to help us.



A portal is a component that renders its contents to another part of the DOM, for example at the end of <body> . This behavior allows you to display elements outside the blocks with, for example, overflow: hidden , but at the same time minimally change the component tree.


Portals are usually used for modal windows (which is also very convenient), but we slightly modify the idea and adapt it to our needs. We need behavior similar to the block with position: absolute and margin-top / margin-left. Let's call this component RelativePortal.


It is convenient to define the interface of the component and only then describe its implementation.
For example, we have the following code:


 <div className="calendarLink"> <a className="calendarLink__trigger"> </a> {isOpen && <CalendarDropdown />} </div> 

When using the portal, the code will change to:


 <div className="calendarLink"> <a className="calendarLink__trigger"> </a> <RelativePortal left="0" top="0"> {isOpen && <CalendarDropdown />} </RelativePortal> </div> 

Now you can get down to business. Every step I will illustrate with an example on jsfiddle. Here is the initial state where you can see the problem - https://jsfiddle.net/Sunify/1k18wxm1/1/ .


How to make a portal (caution - ES6!)


The render method of the portal returns null, so we do not render anything at the place where the component is called. Instead, we will use ReactDOM.render in one of the lifecycle methods of the component, for example, in componentDidUpdate.


 class RelativePortal extends React.Component { ... //  null         render() { return null; } //        componentDidUpdate() { ReactDOM.render( <div {...this.props}>{this.props.children}</div>, this.node ); } ... } 

Half the battle is done, but now our drop-down is displayed at the bottom of the page ( https://jsfiddle.net/Sunify/kr8hehca/ ) - you need to fix this!


 class RelativePortal extends React.Component { ... //   ,  React.findDOMNode(this)   null render() { return <span />; } //         componentDidMount() { this.handleResize = () => { const rect = React.findDOMNode(this).getBoundingClientRect(); const left = window.scrollX + rect.left; const top = window.scrollY + rect.top; if(top !== this.state.top || left !== this.state.left) { this.setState({ left, top }); } }; window.addEventListener('resize', this.handleResize); this.handleResize(); } //              componentDidUpdate() { ReactDOM.render( <div {...this.props} style={{ position: 'absolute', top: this.state.top + this.props.top, left: this.state.left + this.props.left }} > {this.props.children} </div>, this.node ); } ... } 

Now the pop-up is displayed where we both needed and we have a universal component for such tasks.
https://jsfiddle.net/Sunify/4fmdugrr/


Such a method has obvious advantages: it works, it has a minimal interface and it is universal - we can turn anything into RelativePortal.


But it also has a significant drawback - we lose the css cascade. We do not inherit fonts, colors, etc. Does not work: hover - the state of guidance must be stored in the component state. For example, this is https://jsfiddle.net/Sunify/nz7wyee3 . For us, this is not critical, so this solution suits us.


We actively use such a component and placed it in npm. Use !


')

Source: https://habr.com/ru/post/306096/


All Articles