React Async Render on server side using react-async-render
Resolve React async rendering issue on server side, using Redux store.
npm install react-async-render --save
1. What's the problem with React server side rendering?
Simply from the synchronous nature of ReactDOMServer API definition, we know the server side rendering could not be done well if your React application needs to load data asynchronously. That means if you render React application on server side, you are only able to get HTML layout and static contents.
2. How does react-async-render resolve this problem?
In theory, to achieve async server side rendering with React, it needs to:
- register every async request that is defined in every React Component's
constructor
and/orcomponentWillMount
methods that are invoked before the very first-timerender
happens. - get the DOM string when all these registered requests are fulfilled.
To fill these requriments, react-async-render
provides
- a mixin method -
asyncInit
- to allow React Components to register async actions for initialization. - a method -
renderToString
- to render any React Component and return a promise of DOM string.
With leveraging Redux store, react-async-render
resolves this problem by rendering the React Component twice.
- During the first-time render, Components use
asyncInit
method to register async actions, and submit initial data when the actions are done. The initial data of Components will be stored in the Redux store, and the Redux store will be re-used in the second-time render. - When all async actions are done, all the initial data are set in Redux store, the second-time render occurs. The initial data in Redux store could be assigned to Components'
props
attribute, so those Components could use theprops
data to render.
Note: react-async-render
depends on React v0.14
3. How to use react-async-render?
3.1 On React Application side
Include the react-async-render in React Component which needs load data asynchronously
var AsyncRender = ;
Merge the mixin to Component, and set up context for the mixin method
var reactMixin = ;//... require other librariesComponent { superprops context; //... other codes } // ... other methods MyComponentcontextTypes = ...AsyncRendercontextTypes; ;
Then register async actions in Component's consturctor
method, or componentWillMount
method
{ superprops context; // ... other codes thisstate = myData: thispropsdata ; this;}
Assign initial data from Redux store to Component's props
.
var connect = ; // ... MyComponent class { //`initialize` is the reserved namespace in Redux store for `react-async-render` var initStore = stateinitialize || {}; return data: initStoreMyCompInitKey && initStoreMyCompInitKeymyData ;} moduleexports = MyComponent;
3.2 On Node.js server side
Using renderToString
method to render any React Component
var AsyncRender = ;var app = <MyApp> <MyComponent /> <MyOtherComponent /> </MyApp>;AsyncRender;
3.3 Example
Below are complete code examples about how to use react-async-render
in React Components and Express.js server. It will build an AuthorPage
Component, which has AuthorDetail
(No code provided. It has similar code structure to ArticleList
) and Author's ArticleList
.
ArticleList.react.js (read the comments)
// ArticleList.react.js var React = ; var AsyncRender = ; // require this module var request = ; var ArticleItem = ; var reactMixin = ; var connect = ; Component { //context argument is REQUIRED ; thisstate = //this.props.initData will be set from its parent Component data: thispropsinitData ; ifthispropsurl this; } { var data = thisstatedata || ; var items = data; return <div> <ul> items </ul> </div> ; } ; //REQUIRED. mixin this.asyncInit method ArticleListcontextTypes = ...AsyncRendercontextTypes //contextTypes is REQUIRED ; moduleexports = ArticleList;
Load init data from Redux store, and set to data
props of ArticleList
.
AuthorPage.react.js (read the comments)
// AuthorPage.react.js var React = ; var AuthorDetail = ; //Just another React Component var ArticleList = ; var connect = ; Component { ; } { var authorArticlesUrl = `/api/author//articles`; return <div> <AuthorDetail = = // = /> <ArticleList = = // =/> </div> ; } { var idata = stateinitialize || ; //`initialize` is the reserved namespace in Redux store for server rendering return authorData: idataAuthor && idataAuthordata // `Author` match to the initKey set above articleListData: idataAuthorArticles && idataAuthorArticlesdata // `AuthorArticles` match to the initKey set above ; } moduleexports = AuthorPage;
Setting up Express server in server.js (read the comments)
// ... var express = require('express'); // ... var app = express(); // ... other code set up express server var RoutingContext match = ; var routes = ; //React-router route config js var appReducer = ; //if app uses Redux as well var AsyncRender = ; var createLocation = ; //Typical usage with react-router for server side rendering app; { var app = <RoutingContext /> ; //Can be any React Component AsyncRender ; }
Known issue
4. When use react-async-render
to generate the HTML page on server side, the browser will flicker once after loading the HTML page. The reason is that React found the HTML generated server side is different than expected, so it re-renders.
Error codes shown on Developer console like below:
Warning: React attempted to reuse markup in a container but the checksum was invalid.
This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting.
React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server
In 0.0.2, this issue could be fixed with minor changes on both server and client side.
On the server side
var AsyncRender = ;var appReducer = ; //if app uses Redux as wellvar RoutingContext match = ; //... other codes{ var app = <RoutingContext /> ; AsyncRender;}
On the client side
var ReactDOM = ;var Router = ;var routes = ;var appReducer = ;var AsyncProvider createStore = ; //window.__INITIAL_STATE__ is injected into HTML when it serves to clientvar store = ;ReactDOM;
Another solution?
See react-isomorphic-starterkit, which uses react-transmit.
Seemingly this solution has to create one container for every React Component so that it can use its own way to control the rendering.