First: Client side

Let’s install Redux and its helpers:

yarn add redux react-redux redux-thunk

We need to create a reducer in /src/store/appReducer.js, aka a pure function taking two arguments (the previous state, and an action/modifier object) and returning the new state as an immutable object.

const initialState = {

message: null,

}; export const appReducer = (state = initialState, action) => {

switch(action.type) {

case 'SET_MESSAGE':

return {

...state,

message: action.message,

};

default:

return state;

}

};

We’re destructuring the state object here to keep the old properties unchanged, and only replace what we need. In our case, this is not necessarily needed, as we only have the message property, but in a larger application this is what you typically do.

Let’s also write an action creator, that is a function that returns an action object. I usually like to keep things together, so we’ll add this in our reducer file. But feel free to create a separate file if you want to group things differently.

export const setMessage = messageText => ({ type: 'SET_MESSAGE', message: messageText });

Now we’ll create our store initializer in /src/store/configureStore.js.

import {

createStore,

combineReducers,

compose,

applyMiddleware,

} from 'redux'; import ReduxThunk from 'redux-thunk' import { appReducer } from './appReducer'; // if you're using redux-thunk or other middlewares, add them here

const createStoreWithMiddleware = compose(applyMiddleware(

ReduxThunk,

))(createStore); const rootReducer = combineReducers({

app: appReducer,

}); export default function configureStore(initialState = {}) {

return createStoreWithMiddleware(rootReducer, initialState);

};

We’re wrapping our createStore in a function so we can pass the initial state when initializing. This will help us when hydrating the state from the server.

Now let’s use it in our app. Wrap the main App component in a Redux provider in /src/index.js:

import React from 'react';

import ReactDOM from 'react-dom';

import Loadable from 'react-loadable';

import { Provider as ReduxProvider } from 'react-redux' import App from './App';

import configureStore from './store/configureStore'; const store = configureStore(); const AppBundle = (

<ReduxProvider store={store}>

<App />

</ReduxProvider>

); window.onload = () => {

Loadable.preloadReady().then(() => {

ReactDOM.hydrate(

AppBundle,

document.getElementById('root')

);

});

};

Next, we’ll display the message in our App. We’ll also set a default message on the client if the initial value is empty.

import { connect } from 'react-redux';

import { setMessage } from './store/appReducer'; class App extends Component {

componentDidMount() {

if(!this.props.message) {

this.props.updateMessage("Hi, I'm from client!");

}

} render() {

return (

<div className="App">

// ...

<p>

Redux: { this.props.message }

</p>

</div>

);

}

} export default connect(

({ app }) => ({

message: app.message,

}),

dispatch => ({

updateMessage: (txt) => dispatch(setMessage(txt)),

})

)(App);

That’s it! Now run the app with yarn start and see the “Hi, I’m from client!” message displayed after the app loads.

Next: Server side

Remember our serverRenderer middleware which renders our app to a string? Let’s modify that a little bit. We’ll wrap it in another function, so we can pass the store from outsite. We’ll also wrap our main App component in a Redux provider, just like on the client.

export default (store) => (req, res, next) => {

// ...

const html = ReactDOMServer.renderToString(

<ReduxProvider store={store}>

<App />

</ReduxProvider>

);

// ...

}

Now we need to initialize our store and pass it as a prop when using the renderer middleware in our router (in /server/index.js):

import serverRenderer from './middleware/renderer';

import configureStore from '../src/store/configureStore'; //... const store = configureStore();

router.use('^/$', serverRenderer(store)); // ...

In a real application, you will want to move this code in a controller, so you decouple the logic of the app from the initialization of the express server. Also, you’ll want some controller actions that hold more complex logic, maybe even based on the request url. In fact, let’s do this now. We’ll move the code for the router initialization and we’ll write an index action that handles the Redux store initialization in /server/controllers/index.js:

import express from "express";



import serverRenderer from '../middleware/renderer';

import configureStore from '../../src/store/configureStore';



const router = express.Router();

const path = require("path");





const actionIndex = (req, res, next) => {

const store = configureStore();

serverRenderer(store)(req, res, next);

};





// root (/) should always serve our server rendered page

router.use('^/$', actionIndex);



// other static resources should just be served as they are

router.use(express.static(

path.resolve(__dirname, '..', '..', 'build'),

{ maxAge: '30d' },

));



export default router;

As we moved the code in a subdirectory, please make sure that in the route for static files you add an extra ‘..’, so path.resolve() will point to the right location.

Our action is just another middleware that will call the serverRenderer middleware after the Redux store has been initialized. We can even dispatch an action before calling the renderer.

import { setMessage } from '../../src/store/appReducer'; // ... const actionIndex = (req, res, next) => {

const store = configureStore();

store.dispatch(setMessage("Hi, I'm from server!")); serverRenderer(store)(req, res, next);

}; // ...

Now we can clean our /server/index.js entry point:

import express from 'express';

import indexController from './controllers/index'; const app = express();

app.use(indexController); // start the app

// ...

Build the app and start the node server. You should see the message rendered on the server before the client app initializes.

yarn build && node server/bootstrap.js

Finally: Rehydrate the client store from the server

As you may have already noticed, when the client app runs, the store on the client is still empty. As such, the message property will be empty, so the code in the componentDidMount will set our default message, overwriting the one rendered on the server. We need to sync the state on the client with the one on the server.

Because we’re already writing code in our HTML on the server, let’s send the data in the same place. We’ll add a placeholder in /public/index.html:

<div id="root"></div>



<script type="text/javascript" charset="utf-8">

window.REDUX_STATE = "__SERVER_REDUX_STATE__";

</script>

Now, let’s replace this on the server with a JSON representation of our data. Update the serverRenderer:

export default (store) => (req, res, next) => {

// ... const html = ReactDOMServer.renderToString(

<ReduxProvider store={store}>

<App />

</ReduxProvider>

); const reduxState = JSON.stringify(store.getState()); // ... return res.send(

htmlData

.replace(

'<div id="root"></div>',

`<div id="root">${html}</div>`

)

.replace(

'</body>',

extraChunks.join('') + '</body>'

)

.replace('"__SERVER_REDUX_STATE__"', reduxState)

);

}

OK, now we’ll pick this up on the client and initialize the store before rendering the app. Update /src/index.js:

const store = configureStore( window.REDUX_STATE || {} ); const AppBundle = (

<ReduxProvider store={store}>

<App />

</ReduxProvider>

);

Build the app and start the node server one last time.