How to create a React Modal(which is append to `<body>`) with transitions?
Solution 1
At react conf 2015, Ryan Florence demonstrated using portals. Here's how you can create a simple Portal
component...
var Portal = React.createClass({
render: () => null,
portalElement: null,
componentDidMount() {
var p = this.props.portalId && document.getElementById(this.props.portalId);
if (!p) {
var p = document.createElement('div');
p.id = this.props.portalId;
document.body.appendChild(p);
}
this.portalElement = p;
this.componentDidUpdate();
},
componentWillUnmount() {
document.body.removeChild(this.portalElement);
},
componentDidUpdate() {
React.render(<div {...this.props}>{this.props.children}</div>, this.portalElement);
}
});
and then everything you can normally do in React you can do inside of the portal...
<Portal className="DialogGroup">
<ReactCSSTransitionGroup transitionName="Dialog-anim">
{ activeDialog === 1 &&
<div key="0" className="Dialog">
This is an animated dialog
</div> }
</ReactCSSTransitionGroup>
</Portal>
jsbin demo
You can also have a look at Ryan's react-modal, although I haven't actually used it so I don't know how well it works with animation.
Solution 2
I wrote the module react-portal that should help you.
Usage:
import { Portal } from 'react-portal';
<Portal>
This text is portaled at the end of document.body!
</Portal>
<Portal node={document && document.getElementById('san-francisco')}>
This text is portaled into San Francisco!
</Portal>
Solution 3
React 15.x
Here's an ES6 version of the method described in this article:
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
export default class BodyEnd extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
};
componentDidMount() {
this._popup = document.createElement('div');
document.body.appendChild(this._popup);
this._render();
}
componentDidUpdate() {
this._render();
}
componentWillUnmount() {
ReactDOM.unmountComponentAtNode(this._popup);
document.body.removeChild(this._popup);
}
_render() {
ReactDOM.render(this.props.children, this._popup);
}
render() {
return null;
}
}
Just wrap any elements you want to be at the end of the DOM with it:
<BodyEnd><Tooltip pos={{x,y}}>{content}</Tooltip></BodyEnd>
React 16.x
Here's an updated version for React 16:
import React from 'react';
import ReactDOM from 'react-dom';
export default class BodyEnd extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
this.el.style.display = 'contents';
// The <div> is a necessary container for our
// content, but it should not affect our layout.
// Only works in some browsers, but generally
// doesn't matter since this is at
// the end anyway. Feel free to delete this line.
}
componentDidMount() {
document.body.appendChild(this.el);
}
componentWillUnmount() {
document.body.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
Solution 4
As other answers have stated this can be done using Portals. Starting from v16.0
Portals are included in React.
<body>
<div id="root"></div>
<div id="portal"></div>
</body>
Normally, when you return an element from a component's render method, it's mounted into the DOM as a child of the nearest parent node, but with portals you can insert a child into a different location in the DOM.
const PortalComponent = ({ children, onClose }) => {
return createPortal(
<div className="modal" style={modalStyle} onClick={onClose}>
{children}
</div>,
// get outer DOM element
document.getElementById("portal")
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false
};
}
render() {
return (
<div style={styles}>
<Hello name="CodeSandbox" />
<h2>Start editing to see some magic happen {"\u2728"}</h2>
<button onClick={() => this.setState({ modalOpen: true })}>
Open modal
</button>
{this.state.modalOpen && (
<PortalComponent onClose={() => this.setState({ modalOpen: false })}>
<h1>This is modal content</h1>
</PortalComponent>
)}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Check working example here.
Solution 5
The fundamental problem here is that in React you're only allowed to mount component to its parent, which is not always the desired behavior. But how to address this issue?
I've made the solution, addressed to fix this issue. More detailed problem definition, src and examples can be found here: https://github.com/fckt/react-layer-stack#rationale
Rationale
react
/react-dom
comes comes with 2 basic assumptions/ideas:
- every UI is hierarchical naturally. This why we have the idea of
components
which wrap each otherreact-dom
mounts (physically) child component to its parent DOM node by defaultThe problem is that sometimes the second property isn't what you want in your case. Sometimes you want to mount your component into different physical DOM node and hold logical connection between parent and child at the same time.
Canonical example is Tooltip-like component: at some point of development process you could find that you need to add some description for your
UI element
: it'll render in fixed layer and should know its coordinates (which are thatUI element
coord or mouse coords) and at the same time it needs information whether it needs to be shown right now or not, its content and some context from parent components. This example shows that sometimes logical hierarchy isn't match with the physical DOM hierarchy.
Take a look at https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example to see the concrete example which is answer to your question:
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...
Comments
-
jiyinyiyong almost 2 years
There is a modal in this answer https://stackoverflow.com/a/26789089/883571 which is creating a React-based Modal by appending it to
<body>
. However, I found it not compatible with the transition addons provided by React.How to create one with transitions(during enter and leave)?
-
vhs over 8 yearsHere's some further reading on the topic. Another option might be to use
react-motion
. -
James Kyle over 8 yearsThere's also a standalone component for this: github.com/cloudflare/react-gateway
-
DerLola almost 8 yearsThis is a nice solution, but doesn't work if you use the context. This is a problem with many libs like
redux
andreact-intl
. -
thehammer almost 8 yearsI think we also need a
ReactDOM.unmountComponentAtNode(this.portalElement)
incomponentWillUnmount
, no? Otherwise the React tree still lives in memory. It is also in thereact-modal
source github.com/reactjs/react-modal/blob/master/lib/components/…. -
Martijn Pieters over 7 yearsPlease make it explicit that this is your own library, and please read How to offer personal open-source libraries?
-
fckt over 7 years"I propose the solution, addressed to fix this issue" - isn't enough? And this is not only about library, but more about the pattern, library is complementary in this case
-
Martijn Pieters over 7 yearsNo, it is not enough. You are promoting your own library as the proposed solution. You did so in a lot of places. Our community considers such promotion without disclosure to be a form of astroturfing.
-
fckt over 7 yearsok, could we agree first, that this is not clear, barely subjective, controversial topic? What if somebody write answers and explanations instead of me? How it will change the discourse?
-
fckt over 7 yearsbtw, I read to links you shared, all statements are pretty clear, fair and logically consistent IMO. I'm sorry also for raising such a topic, don't sure this is a good place for it..
-
fckt over 7 yearswhat if I'll delete the links to the library? A can delete it from all my topics, not a big deal to me. If ppl will ask, I could share with them privately, no problem )
-
LessQuesar over 7 yearsWhat the! Dude, this should be the #1 answer.
-
Pencilcheck almost 7 yearsgetting uncaught error, can't find element of id ... I found out it is because it is using React to render. It should be ReactDOM instead.
-
Admin over 6 yearsthanks mpen this above is best and simplest way by far and works on Cordova
-
Elad Katz over 6 yearsThe most up-to-date answer as of January 2018... all the way down here.. makes you wonder whether this repeated issue is something that SO should think about
-
mpen over 5 years@OZZIE Try using
ReactDOM.createPortal
instead. I bet that'll work! (Sorry, I don't the full code to post right now) -
OZZIE over 5 years@mpen ah, yeah I realised after posting the comment that the link was to another solution, I though the code you posted was related to this :) would be awesome if this works, although I would be a bit fond of being able to claim that modals aren't well supported in React but now they are :< I hate modals/popups :)
-
mpen over 5 years@OZZIE Haha.. they're not so bad once you have some good functions/components for working with them. I updated the code for React 16.
-
OZZIE over 5 years@mpen I think they are bad UX, except for some rare prompts like "Are you sure ..?"
-
Brady Edgar over 2 yearsThis is an amazing module!! Best answer for me!