How to create a React Modal(which is append to `<body>`) with transitions?
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.
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>
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