Strategies for server-side rendering of asynchronously initialized React.js components Strategies for server-side rendering of asynchronously initialized React.js components reactjs reactjs

Strategies for server-side rendering of asynchronously initialized React.js components


If you use react-router, you can just define a willTransitionTo methods in components, which gets passed a Transition object that you can call .wait on.

It doesn't matter if renderToString is synchronous because the callback to Router.run will not be called until all .waited promises are resolved, so by the time renderToString is called in the middleware you could have populated the stores. Even if the stores are singletons you can just set their data temporarily just-in-time before the synchronous rendering call and the component will see it.

Example of middleware:

var Router = require('react-router');var React = require("react");var url = require("fast-url-parser");module.exports = function(routes) {    return function(req, res, next) {        var path = url.parse(req.url).pathname;        if (/^\/?api/i.test(path)) {            return next();        }        Router.run(routes, path, function(Handler, state) {            var markup = React.renderToString(<Handler routerState={state} />);            var locals = {markup: markup};            res.render("layouts/main", locals);        });    };};

The routes object (which describes the routes hierarchy) is shared verbatim with client and server


I know this is probably not exactly what you want, and it might not make sense, but I remember getting by with slighly modifying the component to handle both :

  • rendering on the server side, with all the initial state already retrieved, asynchronously if needed)
  • rendering on the client side, with ajax if needed

So something like :

/** @jsx React.DOM */var UserGist = React.createClass({  getInitialState: function() {    if (this.props.serverSide) {       return this.props.initialState;    } else {      return {        username: '',        lastGistUrl: ''      };    }  },  componentDidMount: function() {    if (!this.props.serverSide) {     $.get(this.props.source, function(result) {      var lastGist = result[0];      if (this.isMounted()) {        this.setState({          username: lastGist.owner.login,          lastGistUrl: lastGist.html_url        });      }    }.bind(this));    }  },  render: function() {    return (      <div>        {this.state.username}'s last gist is        <a href={this.state.lastGistUrl}>here</a>.      </div>    );  }});// On the client sideReact.renderComponent(  <UserGist source="https://api.github.com/users/octocat/gists" />,  mountNode);// On the server sidegetTheInitialState().then(function (initialState) {    var renderingOptions = {        initialState : initialState;        serverSide : true;    };    var str = Xxx.renderComponentAsString( ... renderingOptions ...)  });

I'm sorry I don't have the exact code at hand, so this might not work out of the box, but I'm posting in the interest of discussion.

Again, the idea is to treat most of the component as a dumb view, and deal with fetching data as much as possible out of the component.


I was really messed around with this today, and although this is not an answer to your problem, I have used this approach. I wanted to use Express for routing rather than React Router, and I didn't want to use Fibers as I didn't need threading support in node.

So I just made a decision that for initial data which needs to be rendered to the flux store on load, I will perform an AJAX request and pass the initial data into the store

I was using Fluxxor for this example.

So on my express route, in this case a /products route:

var request = require('superagent');var url = 'http://myendpoint/api/product?category=FI';request  .get(url)  .end(function(err, response){    if (response.ok) {          render(res, response.body);            } else {      render(res, 'error getting initial product data');    } }.bind(this));

Then my initialize render method which passes the data to the store.

var render = function (res, products) {  var stores = {     productStore: new productStore({category: category, products: products }),    categoryStore: new categoryStore()  };  var actions = {     productActions: productActions,    categoryActions: categoryActions  };  var flux = new Fluxxor.Flux(stores, actions);  var App = React.createClass({    render: function() {      return (          <Product flux={flux} />      );    }  });  var ProductApp = React.createFactory(App);  var html = React.renderToString(ProductApp());  // using ejs for templating here, could use something else  res.render('product-view.ejs', { app: html });