What do React Portals solve? - javascript

From the docs of react portal:
A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually “break out” of its container. For example, dialogs, hovercards, and tooltips.
The suggested solution is to use:
// this line is rendered in `Portal1` component,
// which is rendered in `Parent1` component.
ReactDOM.createPortal(Child1, Container1)
I don't understand what does it solves. Why making Child1 child of Container1 instead of Parent1 helps?
My question maybe not clear so if it doesn't -> How does this solution differ from other solutions for creating "dialogs, hovercards, and tooltips"?

When you initialise a React application, ReactDOM tells one DOM container that all its React components will be rendered under this DOM. This makes React do all rendering processing.
Sometimes you need to control a React Component to render as a child to a different DOM element, and continue to interact with your React application. This is why we use React Portals
As React creates virtual elements under the hood, you cannot convert the into DOM elements and insert them directly into the DOM. React Portals allows to you pass a React Elements and specify the container DOM for the React Element
Here is an example:
You have a Modal component which renders a div element in the center.
function Modal() {
return (
<div style={{ position: 'absolute', left: '50%'}}>
Message
</div>
);
}
One puts your Modal component inside a div of relative position.
<div style={{ position: 'relative', left: 100 }}>
<Modal />
</div>
The problem is when Modal component is rendered, its position is relative to parent div's position but you need to show it at the centre of window.
In order to solve this problem, you can append your Modal component directly to the body element with a portal
Here is the solution with Portals.
function ModalRenderer() {
return (
React.createPortal(
<Modal />,
document.body
)
);
}
And use ModalRenderer component anywhere inside your application.
<div style={{ position: 'relative', left: 100 }}>
<ModalRenderer />
</div>
ModalRenderer has the container element for the Modal which is outside of the DOM tree, but still within the React Application tree

In React V15,we can only add children dom into the father dom.That means, if you want to have an element, you have to create a new div.Like this:
<div>
{this.props.children}
</div>
In React v16,don't need to create a new div.We can use portal to add the children element to any dom in the dom tree.
ReactDOM.createPortal(
this.props.children,
domNode
);
overflow: hidden or z-index style
If a parent component has an overflow: hidden or z-index style, and the children element type is dialogs, hovercards, tooltips and so on,these should be on the upper layer of the father element, meaning break out.But they maybe shade by the father component.
So createPortal offers a better option.It can load on the upper component of the father component.After mounting the element to another dom,it won't be sheltered.
Event and bubble up
Even the component mounted on another component, event can budde up to the father component.

One good case is to separate CSS concerns.
Here is an example:
HTML
<div id="app-root"></div>
<div id="modal-root"></div>
CSS
.app {
position: fixed;
height: 100%;
width: 100%;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.modal {
background-color: rgba(0,0,0,0.5);
}
Babel
const appRoot = document.getElementById('app-root')
const modalRoot = document.getElementById('modal-root')
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: false
}
this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
}
handleShow() {
this.setState({
showModal: true
})
}
handleHide() {
this.setState({
showModal: false
})
}
render() {
const modal = this.state.showModal ? (
<Modal>
<div className="modal">I am no longer centered!</div>
</Modal>
) : null
return (
<div className='app'>
Basic
<button onClick={this.handleShow}>
Show Modal
</button>
{modal}
</div>
)
}
}
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount(){
modalRoot.appendChild(this.el)
}
componentWillUnmount() {
modalRoot.removeChild(this.el)
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el
)
}
}
ReactDOM.render(<App />, appRoot)
Observations:
1. The main text has fixed position and is centered
2. When we click the button, the text popped up. Notice the text is no longer centered!
3. If we had used it to something like this:
<div className='app'>
Basic
<button onClick={this.handleShow}>
Show Modal
</button>
<div className="modal">I am centered ;(</div>
</div>
Notice the text is centered. Modal works by going to modal-root and attach that DOM element there. You can have your own CSS under modal-root hood, separate from parent component (app-root).
You are now no longer obligated to attach your "child" components under your parent's. In this case, you (app-root) are attaching it to its sibling (modal-root). You can totally attach it to document.body, or whatever element you wanted to. Another perk is, like other user mentioned, event bubbling happens as if that child component is their own child.
A Parent component in #app-root would be able to catch an uncaught, bubbling event from the sibling node #modal-root.
Source

Related

Can you render react component to div with display none?

I need to render react component to div with id hover-countdown. So as the name says it is not visible when page load, but only on hover.
Does this somehow affect ReactDOM render? Because i am not able to render it.
Is there some other way?
Thank you:)
const HoverCountdown = () => {
return (
<span>Countdown</span>
);
};
document.addEventListener("DOMContentLoaded", () => {
ReactDOM.render(
<HoverCountdown />,
document.getElementById("hover-countdown")
);
});
Does this somehow affect ReactDOM render?
Not really. It React only concerns itself with DOM updates.
If the element it renders inside is display: none then the React rendering process will generate the DOM inside that element.
Then, subsequent to the React rendering, the browser will convert the DOM to something rendered on screen (which will be nothing because the container is display: none).
If you set the visibility to hidden, hovering over the invisible component will not trigger the :hover styles to take effect.
What is odd is that if you inspect element and toggle the :hidden styles then it will properly show up.
const HoverCountdown = () => (
<span>Countdown</span>
);
//document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<HoverCountdown />,
document.getElementById('hover-countdown')
);
//});
.wrapper {
background: #EFE;
}
#hover-countdown {
visibility: hidden; /* will render fine if removed */
}
#hover-countdown:hover {
visibility: visible;
color: #040;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div class="wrapper">
<div id="hover-countdown"></div>
</div>

How to blur/disable parent component when child page (popup page) is opened?

I am working with React. I want to know about the easiest way on how to to blur parent component when child page (popup page) is opened.
Here is my Fiddle Workspace Demo
Even if I blur it the parent should still be clickable
Can anyone show how to achieve this functionality?
We can use CSS to blur the parent component. Something like this:
We will wrap the child component into a div with the class name as overlay
Apply CSS effect to blur the parent component
[Note: You can also add the click event to the blur areas by adding the event to the overlay div]
{this.state.childOpen && (
<div className="overlay">
<div className="overlay-opacity" />
<Child data={data} applyFilter={this.applyFilter} />
</div>
)}
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.overlay-opacity {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: black;
opacity: 0.5;
}
Here is the live demo
To hide the child component onclick of the blur area, we can add a event like this
hideChild() {
this.setState({
childOpen: false
});
}
Here is the live demo
Hope it helps :)
Since you are working on react, I would like to suggest you to use react-responsive-modal library. You can install this using npm.
It is easily customizable via props.
Please find the example below:
https://codesandbox.io/s/9jxp669j2o
What i understood is that you want to blur the table when child is opened
Here is code you need to add
formatStyle = () => {
if (this.state.childOpen == true) return { filter: "blur(5px)" };
else return {};
};
//add formatStyle in main app
styleData={this.formatStyle()} // in props on table
style = {this.props.styleData} //add this attribute in main container of table component
styleData={this.formatStyle()} // Pass this in ReactTable in main app
The feature you are looking for is already in Semantic-UI-React Modal. This is very easy to use, you only have to pass some props to get above result.
demo https://react.semantic-ui.com/modules/modal/#variations-dimmer

Scroll position of a div id in parent component

Using ReactJS and GatsbyJS (v2) I have a header component that sits fixed over div id="outerContainer" in the parent layout component.
To toggle class on scroll position below 100px, I would generally use window.scrollY < 100.
However, due to the body class with the style overflow:hidden and outerContainer with overflow: scroll, the window scroll position does not change.
How can I define outerContainer.scrollY < 100 from a child component, whilst referencing the outerContainer of the parent for scroll position?
Layout.js
const Layout = ({ children }) => (
<div>
<div id="outerContainer" ref={el => (this.outerContainer = el)}>
<div className="content">{children}</div>
</div>
<Header />
</div>
);
Header.js
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
navScrolled: true
};
this.onScroll = this.onScroll.bind(this);
}
componentDidMount() {
document.addEventListener('scroll', () => {
const navScrolled = outerContainer.scrollY < 100;
if (navScrolled !== this.state.navScrolled) {
this.onScroll(navScrolled);
}
});
}
componentWillUnmount() {...}
...
}
My first thought is that I should perform function in layout.js and use props pass the state down. Is this the best option or perhaps is there another way that would keen everything in header.js?
Your first thought is correct, parent class is proper place for your onScroll callback, because the scroll position is a property of your entire layout, not of header which only uses it.
Also, by keeping onScroll in parent class, you keep yourself able to pass it to outerContainer as well, if it grows into separate class during the development, which is likely to happen.

Hide and Show Looped Components in React

I have a series of questions for a sign-up flow I am building. Currently, I am looping through each components and displaying them all on one page. My questions are, How do I show only one at a time? How can I include a slide left transition/animation when each slide hides/shows? I would like each question to display individually then once the user clicks next, it hides the first question and displays the second. I am a bit newer to React so I apologize if this is a basic question but I cannot figure it out.
Below are breakouts of my code:
import React from 'react';
import Q1Name from './questions/Q1Name';
import Q2Birthday from './questions/Q2Birthday';
import Q3City from './questions/Q3City';
import Q4YouReady from './questions/Q4YouReady';
import Q5Setting from './questions/Q5Setting';
import Q6Length from './questions/Q6Length';
import Q7Email from './questions/Q7Email';
class SignUpPage extends React.Component {
render() {
const components = [Q1Name, Q2Birthday, Q3City, Q5Setting, Q6Length, Q7Email];
const componentsToRender = components.map((Component, i) => (
<Component key={i} />
));
return (
<div className = "container-fluid">
<div className = "question-box">
{componentsToRender}
</div>
</div>
);
}
}
export default SignUpPage;
This is an example component - they are all slightly different so I am showing the two primary types:
the first only has a single "next button"
import React from 'react';
class Q2Birthday extends React.Component {
render() {
return (
<div className="questions">
<h1 id="question-h1">When is your birthday?</h1>
<form>
<div className="form-group">
<input type="date" className="form-control custom-form" id="birthdayInput" aria-describedby="birthday" placeholder="" />
</div>
<button type="submit" className="btn btn-custom btn-lg">Next Question!</button>
</form>
</div>
);
}
}
export default Q2Birthday;
the second has 3 button options the user can select from
import React from 'react';
class Q6Length extends React.Component {
render() {
return (
<div className="questions">
<h1 id="question-h1">How long would you like your trip to be?</h1>
<button type="submit" className="btn btn-custom-select btn-lg">Just a weekend!</button>
<button type="submit" className="btn btn-custom-select btn-lg">A full week!</button>
<button type="submit" className="btn btn-custom-select btn-lg">I'm flexible!</button>
</div>
);
}
}
export default Q6Length;
I also want to add in a slide left transition for the "questions" div within the question-box class. I have been reading up on react-transition-group but I am a bit confused on how to implement it. Also, with this application, I do not need to store the values of the form data.
How do I show only one at a time?
Given that you want to do a slide transition between them, you need at least the one being left behind and the next one to show to be in the DOM at switchover time. When not switching it's possible to have only the current one in the DOM. But simplest would probably be to always have all of them in the DOM, just with the previous/next ones out of the left/right sides of the viewport.
So to answer the question of how to show just one at a time, one way would be to translate all "old" ones left by 100% of the container width, leave the current one be, and translate all "next" ones right by 100%.
Styles for that might look like this:
const oldStyle = {
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
transform: 'translate(-100%)',
};
const currentStyle = {
position: 'relative',
transform: 'translate(0)',
};
const nextStyle = {
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
transform: 'translate(100%)',
};
Depending on where this is on your page, you might need some additional styles to hide the overflowing next/previous slides. Look up the overflow property. You may also need to fix heights or widths of the container -- look into this if you find the non-current slides have an unexpected (such as zero) height or width.
To apply the appropriate styles to each panel, you'll need to know which is which.
I'd suggest keeping track of the current slide index in your parent component's state. Say you have this in this.state.currentSlide. With that, you can choose the slide styles like this:
const componentsToRender = components.map((Component, i) => (
<Component key={i} style={i < this.state.currentSlide ? oldStyle : i === this.state.currentSlide ? currentStyle : nextStyle} />
));
In order for that style prop to pass through to your slides, you'd need to tweak the slides a little. The simplest way would just be to explicitly pass that one through:
<div className="questions" style={this.props.style}>
But how do we set the current slide in state, and keep it up to date? Well, in the simplest case, you need to initialize it at zero. Your slide components will need to tell the parent when they've finished. And you'll need to notice that, and update the state.
class SignUpPage extends React.Component {
constructor(props) {
super(props);
// Set initial state
this.state = {
currentSlide: 0,
};
}
// Handle notification from a child slide that we should move to the next
nextSlide() {
this.setState({
currentSlide: this.state.currentSlide + 1,
});
}
render() {
...
const componentsToRender = components.map((Component, i) => (
<Component key={i} style={this.props.style} onNext={this.nextSlide.bind(this)} />
));
The child components then need to call this method which has been passed in when they're finished:
class Q2Birthday extends React.Component {
handleSubmit(event) {
// Don't perform an actual form submission
event.preventDefault();
// Notify the parent
this.props.onNext();
}
render() {
return (
<div className="questions" style={this.props.style}>
<h1 id="question-h1">When is your birthday?</h1>
<form onSubmit={this.handleSubmit}>
...
How can I include a slide left transition/animation when each slide hides/shows?
With the above styles, it might be as simple as setting the following style for each of the slides:
transition: transform 0.5s;

How do I get the dom node of a child element in a component without adding logic to its parent/children?

In the following example WrapperComp needs to get access to the dom node of the divs in line 5 and line 8, without adding logic to PageComp or ItemComp. The only things I could change in PageComp are the div tags. E.g. I could add a ref, prop, data-attribute, etc to them.
The divs don't have to be created inside PageComp. WrapperComp would be allowed to create them too, but they must wrap each of its children (In this case each ItemComp).
Example
class PageComp extends React.Component {
render() {
return (
<WrapperComp>
<div>
<ItemComp/>
</div>
<div>
<ItemComp/>
</div>
</WrapperComp>
);
}
}
class WrapperComp extends React.Component {
render() {
return (
<div>
<h1>A wrapper</h1>
{this.props.children}
</div>
);
}
}
class ItemComp extends React.Component {
render() {
return (
<div>
<h1>An item</h1>
</div>
);
}
}
ReactDOM.render(
<PageComp/>,
document.getElementById('app')
);
JSBIN
What I tried so far:
I already tried to put a ref= on the divs, but that ref would only be available in PageComp not in WrapperComp.
I also tried to create the divs inside WrapperComp and put a ref= on them from there, but that would result in a Refs Must Have Owner Warning
Now I wonder.. what would be an appropriate way in react to solve that problem?
Till now the only solution that came to my mind was to put a data-attribute on each div and search the dom for them after componentDidMount like that: document.querySelectorAll('[data-xxx]'). Perhaps I'm not sure if this is how you do it in react..
Why do I want to get the node inside WrapperComp?
I want to create a component that adjusts the dimensions of its children. In the example that component would be WrapperComp. The adjustments can only be done after the children rendered to the dom, e.g. to get clientHeight.
If you don't restrict that this needs to be solved by how one should get the DOM, pass them down, etc, I would get rid of the puzzle and approach it in a different direction.
Since you are not given much control to <PageComp> whereas <WrapperComp> seems flexible, I would do the wrapping in the later by transforming the passed children to what you need them to be.
class PageComp extends React.Component {
render() {
return (
<WrapperComp>
<ItemComp/>
<ItemComp/>
</WrapperComp>
);
}
}
class WrapperComp extends React.Component {
render() {
const wrappedChldren = React.Children.map(this.props.children, function(child) {
return (
<div ref={function(div) {
this.setState{clientHeight: div.clientHeight}
}}>
<h1>A wrapper</h1>
{ child }
</div>
);
});
return <div>{ wrappedChildren }</div>;
}
}
With this concentrate can be put on the transformation in the <WrapperComp>, which is pretty intuitive as its name suggests.

Categories

Resources