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,
);
}
}
Working example
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.