Few days back Tyler McGinnis had shared a great video to build a simple Universal React app with React Router. If you haven’t seen it, i would definitely recommend you to watch it once. It was a great article/video explaining the pain points of configuring a universal react app.

https://medium.com/@tylermcginnis/server-rendering-with-react-and-react-router-e0b7ba37653f

I would like to continue on that sample app and walkthrough the steps involved in configuring Redux within it for state management. So, lets straight away dig into it. I would be using Tyler’s boilerplate as a starting point.

Clone Tyler’s boilerplate from the below location :- https://github.com/tylermcginnis/rrssr

Installing the required libraries :- npm install --save react-router-redux@next react-redux redux redux-thunk history

Next, we create a store at our server side, populate it and then send it to the client wherein again the store is initialized with the data we sent from server. So let’s create a function to configure our store. You could see that we accept another optional parameter history which we need for react-redux-router . We will get back to that later. Along with that, we would configure our required middlewares (like redux thunk, redux dev tools extensions) import { compose , createStore , applyMiddleware } from ' redux ' import thunk from ' redux-thunk ' import rootReducer from ' ./rootReducer ' import { routerMiddleware } from ' react-router-redux ' export default function configureStore ( initialState , history = null ) { /* Middleware * Configure this array with the middleware that you want included */ let middleware = [ thunk ] if ( history ) { middleware . push ( routerMiddleware ( history )) } // Add universal enhancers here let enhancers = [] const composeEnhancers = ( typeof window !== ' undefined ' && window . __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ) || compose const enhancer = composeEnhancers ( ...[ applyMiddleware (... middleware ), ... enhancers ] ) // create store with enhancers, middleware, reducers, and initialState const store = createStore ( rootReducer , initialState , enhancer ) if ( module . hot ) { // Enable Webpack hot module replacement for reducers module . hot . accept ( ' ../reducers ' , () => { const nextRootReducer = require ( ' ../reducers ' ). default store . replaceReducer ( nextRootReducer ) }) } return store }

At the server side, the fetching of data depends on the route invoked by the user. Inside each of the component which depends on some kind of data from the API, we will have a static function which dispatches the actions to fetch the data and updates the store. So, we have the call to dispatch action at 2 places :- Static function : This will be called from server side.

: This will be called from server side. ComponentDidMount: This will be only called at client side provided the server fetch didn’t happen/failed. We will have the check in componentDidMount to dispatch the action only if we dont have the data already in redux store. import React , { Component } from ' react ' import { connect } from ' react-redux ' import { bindActionCreators } from ' redux ' import * as Actions from ' ./Actions ' class Sample extends Component { // This will be called at server side to do the fetching and populating redux store static fetchData ( store ) { return store . dispatch ( Actions . getSampleData ()) } async componentDidMount () { // check whether store already has data? if ( ! this . props . sampleData ) { await this . props . actions . getSampleData () } } render () { return ( < div > < h2 > Sample Data </ h2 > < h3 > { this . props . sampleData . name } </ h3 > </ div > ) } } function mapStateToProps ( state , ownProps ) { return { sampleData : state . sample } } function mapDispatchToProps ( dispatch ) { return { actions : bindActionCreators ( Actions , dispatch ) } } export default connect ( mapStateToProps , mapDispatchToProps )( Sample )

At server side, we get the component, which would eventually be rendered by the route and then call its function to populate the store. app . get ( ' * ' , ( req , res , next ) => { let responseBody = null // Create a new Redux store instance const initialState = { sample : {} } const store = configureStore ( initialState ) const markup = renderToString ( < Provider store = { store } > < StaticRouter location = { req . url } > < App /> < /StaticRouter > < /Provider > ) const activeRoute = routes . find (( route ) => matchPath ( req . url , route )) || {} if ( activeRoute . component && activeRoute . component . fetchData ) { // calling the static method of the component to populate store activeRoute . component . fetchData ( store ). then (() => { responseBody = AppShell ( store . getState (), markup ) res . send ( responseBody ) }) } else { responseBody = AppShell ( store . getState (), markup ) res . send ( responseBody ) } })

We pass in the the redux store state to the AppShell function which sets the store to the window object so that we can access it at the client side. This is one of the easiest way to transfer content from server side to client side. Please be cautious of passing in any sensitive information here. <script>window.__INITIAL_STATE__ = ${serialize(state)}</script>