Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to render asynchronously initialized components on the server

I'm relatively new to ReactJS and have been seduced by the ease of implementing server-side rendering to reduce "time to first tweet". I'm running a Node-Express-React stack which pre-renders the markup on the server using React's renderComponentToString.

It works fine when the components can be rendered synchronously, however I'm struggling when it comes to implementing ajax-populated components (however this applies to any asynchronous operation during the initialization of the component, e.g. websocket).

Take the example from the React website: http://facebook.github.io/react/tips/initial-ajax.html

componentDidMount: function() {
 $.get(this.props.source, function(result) {
  var lastGist = result[0];
  this.setState({
    username: lastGist.user.login,
    lastGistUrl: lastGist.html_url
  });
}.bind(this));

It won't work on the server, since componentDidMount is never triggered when using renderComponentToString. This simple case can be worked around by using the same HTTP request wrapper on the client and on the server (instead of using jQuery's $.get), and by pre-fetching the data before instantiating the component and passing it as a prop.

However, in an actual, complex app, asynchronous dependencies can get very complicated and pre-fetching doesn't really fit the descendant approach of building React structures. How can I implement an asynchronous initialization pattern in React that can be rendered on the server without actually mounting anything (i.e. no DOM emulation a la PhantomJS, which is the whole point of using renderComponentToString) ?

like image 914
elierotenberg Avatar asked Dec 28 '13 14:12

elierotenberg


People also ask

What is asynchronous rendering?

Unlike with synchronous, a user visiting pages that utilize the asynchronous mode gets to see the page content without having to wait for the page to display the whole content. The type of rendering mode loads pages independently from the ads.

Is react synchronous?

React sets its state asynchronously because doing otherwise can result in an expensive operation. Making it synchronous might leave the browser unresponsive. Asynchronous setState calls are batched to provide a better user experience and performance.


2 Answers

I believe the most practical way to do this is to make an optional prop for the preloaded data, like so:

getInitialState: function() {
    if (this.props.initialLastGist) {
        var lastGist = this.props.initialLastGist;
        return {
            username: lastGist.user.login,
            lastGistUrl: lastGist.html_url
        };
    } else {
        return {};
    }
},

componentDidMount: function() {
    if (!this.props.initialLastGist) {
        $.get(this.props.source, function(result) {
            var lastGist = result[0];
            this.setState({
                username: lastGist.user.login,
                lastGistUrl: lastGist.html_url
            });
        }.bind(this));
    }
},

With a setup like this, the component can be rendered immediately if the preloaded data is present; otherwise the AJAX request will be sent while mounting.

Currently, server rendering is always synchronous and componentDidMount isn't called on the server because it usually involves DOM manipulation. Sorry I don't have a better solution here right now, but in general you want to minimize the number of HTTP requests to the server so it's worth thinking through your architecture so that you can collect all the data you need on the server.

like image 150
Sophie Alpert Avatar answered Oct 23 '22 19:10

Sophie Alpert


You could take a look at the react-quickstart project, which uses react-async and ships with a server-rendering example. react-async provides a decorated renderComponentToString method that pre-fetches asynchronous state and renders it into the initial markup. However, you will need to specify asynchronous state separately from 'regular' state.

like image 27
jxg Avatar answered Oct 23 '22 18:10

jxg