Rendering React components to the document body

One of React’s best features is the simplicity of it’s API. A component can be as simple as a render method which returns the component’s structure – just write a simple function, and you have a useful, re-usable piece of code. There are times where this can be limiting, however. In particular, the fact that this API gives a component no control over where in the DOM it will be mounted makes popup components (like modals or menus) difficult to implement. Problems arise when a parent element has overflow set to 'hidden', for example, causing something a little like this:

Screen Shot 2015-03-01 at 11.22.40 pm When what we really want is this: Screen Shot 2015-03-01 at 11.26.04 pm Luckily, there is a fairly elegant way around this – although it isn’t glaringly obvious. One of the very first React functions that anybody will learn is React.render, with the signature:

ReactComponent render(
  ReactElement element,
  DOMElement container,
  [function callback]
)

Usually, we’d use this to render the entire application to a single DOM element. Happily, it isn’t limited to this. In fact, we can use

React.render within one component to render another component to a completely different DOM element. As a component’s render function must be pure (i.e. it cannot change state or interact with the DOM), we should instead call React.render from componentDidUpdate or componentDidMount. Additionally, we need to make sure that any rendered component is properly unmounted when our parent component unmounts.

Putting it all together, we can build a component to solve this for us:

var RenderInBody = React.createClass({

  componentDidMount: function() {
    this.popup = document.createElement("div");
    document.body.appendChild(this.popup);
    this._renderLayer();
  },


  componentDidUpdate: function() {
    this._renderLayer();
  },


  componentWillUnmount: function() {
    React.unmountComponentAtNode(this.popup);
    document.body.removeChild(this.popup);
  },


  _renderLayer: function() {
    React.render(this.props.children, this.popup);
  }


  render: function() {
    // Render a placeholder
    return React.DOM.div(this.props);
  }

});

Then, whenever we want to escape our parent component’s DOM and render directly to document.body, all we need to do is wrap our component’s output in <RenderInBody>, like so:

var Dialog = React.createClass({
  render: function() {
    var dialogPopup = <DialogPopup {...this.props} />;

    return (
      <RenderInBody>{dialogPopup}</RenderInBody>
    );
  }
});

If you value your time and want to create Single Page Applications which people love, subscribe to my newsletter! In return for your e-mail, you’ll also immediately receive 3 bonus print-optimised PDF cheatsheets – on React, ES6 and JavaScript promises.

I will send you useful articles, cheatsheets and code.

I won't send you useless inbox filler. No spam, ever.
Unsubscribe at any time.
Share on Facebook0Tweet about this on TwitterShare on LinkedIn0Share on Reddit0

9 Comments Rendering React components to the document body

  1. Mark Allen

    Nice post James! My team wanted to do something like this so we could add popover/drop-down options to components without worrying if all their containing elements had the proper CSS styles (overflow visible, relative position, etc). We decided it would be more consistent to render straight to the document body and position the popovers with javascript.

    Although this is a relatively old post (in React time), because we used this code sample as a starting point for our solution, I wanted to point out one small, but consequential pitfall of the approach:

    When you call React.render(this.props.children, this.popup); every time, you miss out on many of the great parts of React like the virtual DOM, it’s speed, and the diffing that occurs when the component is re-rendered into the DOM.

    The approach we settled on involves saving a reference to the rendered component when we render it into the DOM on componentDidMount. We can then setState whenever the component receives updates, and utilize the power of React’s virtual DOM! Only the elements that have changed will be updated.


    componentDidMount: function () {
    this._container = document.createElement("div");
    document.body.appendChild(this._container);
    this._child = React.render(this.props.children, this._container);
    },

    componentDidUpdate: function () {
    if (!this._child) return;

    this._child.setState({});
    },

    componentWillUnmount: function () {
    React.unmountComponentAtNode(this._container);
    document.body.removeChild(this._container);
    }

    Works like a charm. Integration remains exactly the same as the code you posted.

    In other news, I also learned today that you can return null from a component’s render function instead of an empty div! React will render an empty noscript tag into the DOM instead of the empty div, so you can be completely sure it does nothing (:


    render: function () {
    return null;
    }

    Thanks again!

    Reply
    1. Richard

      This didn’t seem to work with your componentDidUpdate function. I had to put the ReactDom.render back in.

      Reply
      1. Jason

        I am also experiencing issues with the componentDidUpdate() here. It seems the example works excellent up to the point where if something updates. My props aren’t getting passed down into the child component. Is this what you were seeing also? What did you have to do to fix this?

        Reply
        1. Jason

          This is what I ended up doing in order to get it to update my child component with the new props, however it won’t be using the virtual dom anymore with this way. Does anyone have ideas on how we can get this to work the right way? The componentDidUpdate is where my main concerns lie. Everything else seems to work fine from what I can tell.

          export class RenderInBody extends React.Component {
          private container: Element;
          private child: any;

          componentDidMount() {
          this.container = document.createElement(“div”);
          document.body.appendChild(this.container);
          this.child = ReactDOM.render(this.props.children, this.container);
          }

          componentDidUpdate() {
          if (!this.child) return;
          this.child = ReactDOM.render(this.props.children, this.container);
          this.child.setState({});
          }

          componentWillUnmount() {
          ReactDOM.unmountComponentAtNode(this.container);
          document.body.removeChild(this.container);
          }

          render() {
          return null;
          }
          }

          Reply
  2. Jean

    Hi, this solution definitely looks like what I need to handle dialogs.
    But I don’t really get it… what should the dialog do to unmount the RenderInBody component?
    In other words, how do we get to have “componentWillUnmount” called?
    Thanks!

    Reply
    1. Jean

      Ok I was misunderstanding. This piece of code is to make sure that the child component (popup) is destroyed when the parent unmounts. Not as I thought a helper to create/destroy only the popup. But is will be a good basis for this purpose anyhow.
      Thanks for this post.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *