Tutorial: Handcrafting an Isomorphic Redux Application (With Love)

Milo Mordaunt
16 min readAug 2, 2015

2019 UPDATE: As a word of warning this tutorial is very old at this point! I would not use it as a reference, or really expect it to work, though Redux and the ideas behind it have not changed much, you may want to look elsewhere!

Well, I know you like Todo lists, I mean I know you really like writing Todo lists, so I want you to be able to build one using the delicious and nutritious new Flux framework, Redux. I want the best for you.

In this article you’ll learn how to forge your very own Redux application, not limited to but including:

  • Wholegrain server-side rendering
  • Extensible routing rich in Omega-3
  • Buttery asynchronous data loading
  • A Smooth functional after-taste

If that sounds like something you want in your life, read on, if not, don’t bother.

I will say this: it didn’t turn out to be a super small tutorial, so strap yourself in and get ready for a bumpy ride, keep your hands and feet in the vehicle at all times etc etc.

UPDATE (16/09/15): Sorry to people who have had trouble with the router (missing Location and History objects, inconsistent API use) over the past few weeks. I have updated the article (and repo) to reflect react-router@1.0.0-rc3, and since it’s an RC the API surface should be stable with 1.0.0 now. If you spot any inconsistencies I am likely referencing old code, please leave a comment!

Wait, hold up, what’s a Redux?

Oh, I’m glad you asked!

Redux is new Flux framework by Dan Abramov, which removes a lot of unnecessary complication. You can read about why the framework was built here, but the TL; DR is that Redux holds your application state in one place, and defines a minimal but sufficiently powerful way of interacting with that state.

If you’re familiar with traditional Flux frameworks, then the largest difference you’ll notice is the absence of Stores, and presence of ‘Reducers’. In Redux, all the state lives in one place (the ‘Redux’ instance), instead of being split into Separate stores (which can get a little yucky with isomorphism). A ‘Reducer’ is just a description of how that state should change, it doesn’t mutate anything at all, and looks something like this:

function exampleReducer(state, action) {
return state.changedBasedOn(action)
}

As we’ll see more later.

You should probably think of Redux less as a framework and more as a suggestion. It is a very minimal base, which takes all the best ideas of Flux. This article will hopefully teach you how to use it successfully, go forth and prosper with minimal mutation.

Making yourself comfortable

We’re going to use Webpack and Babel to string together our application, because we’re cool, hip and happening, and because they give you hot reloading and hot ES6/7 features, respectively.

Firstly we should create a new directory and lay down a few sick files.

Here’s a package.json I made earlier:

And a webpack.config.js:

And a .babelrc (for added ‘ES7’ syntactic sugar):

{
"optional": ["es7.decorators", "es7.classProperties", "es7.objectRestSpread"]
}

There honestly isn’t much of interest in these files, they are just to set up a sensible dev environment. We’ll need to run npm i to pull in the dependencies and we should be good to go.

Serve me Seymour

The basic structure of this app will look as follows:

client/
shared/
index.js
server.jsx

The vast majority of the code will be in the ‘shared’ directory, but some glue code is needed to separate the client and server cleanly and with future extensibility in mind.

index.js is just a file to bootstrap server.jsx, so we can use ES6/JSX ASAP. We can use something like:

'use strict';require('babel/register')({});var server = require('./server');const PORT = process.env.PORT || 3000;server.listen(PORT, function () {
console.log('Server listening on', PORT);
});

The server here will be an Express application, because it’s easy, and chances are you know it well. This in server.jsx should do the trick for now:

import express from 'express';const app = express();app.use((req, res) => {
const HTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Isomorphic Redux Demo</title>
</head>
<body>
<div id="react-view"></div>
<script type="application/javascript" src="/bundle.js"></script>
</body>
</html>
`;

res.end(HTML);
});
export default app;

Pretty standard stuff. We set up an Express server with a global middleware, but don’t actually serve much yet, just a blank web page to help us stare into the void of existence. Let’s fix that.

Routing like a pro

You might think it’d be easy to Express’ routing and templating. Unfortunately you’d be wrong, since we want to share as much code between the server and the client for the future’s sake. We’re going to use React Router in this article, since it makes server-side and client-side rendering a cinch.

We’ll have a root component at shared/components/index.jsx, which React Router can inject everything else into. This way we can easily add global app aesthetics (think headers and footers, for instance), nice architecture for a shiny SPA.

import React from 'react';export default class AppView extends React.Component {
render() {
return (
<div id="app-view">
<h1>Todos</h1>
<hr /> {this.props.children}
</div>
);
}
}

The children prop here will be the component tree that the router gives us after it’s dependency magics. Here we don’t do anything special, just render them as-is.

Note that we are using the 1.0.0 beta of React Router (as defined in the package.json above). The older version has a different API, which is the one you will see by default when visiting their site. A stable version will be released soon, but until then bear this in mind.

Next we should define a route at shared/routes.jsx:

import React     from 'react';
import { Route } from 'react-router';
import App from 'components';export default (
<Route name="app" component={App} path="/">
</Route>
);

Here we just tell React Router to render our components/index at /. Sounds good to me! Despite all our hard work both the server and the client are still oblivious to all of this though, so we should tell them next by updating our server, which can now look as below.

server.jsx:

import express                   from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server'
import { RoutingContext, match } from 'react-router';
import createLocation from 'history/lib/createLocation';
import routes from 'routes';
const app = express();app.use((req, res) => {
const location = createLocation(req.url);
match({ routes, location }, (err, redirectLocation, renderProps) => {
if (err) {
console.error(err);
return res.status(500).end('Internal server error');
}
if (!renderProps) return res.status(404).end('Not found.');

const InitialComponent = (
<RoutingContext {...renderProps} />
);
const componentHTML = renderToString(InitialComponent); const HTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Isomorphic Redux Demo</title>
</head>
<body>
<div id="react-view">${componentHTML}</div>
<script type="application/javascript" src="/bundle.js"></script>
</body>
</html>
`
res.end(HTML);
});
});
export default app;

Here we import some new toys and tell the router to route the request express hands over. Hopefully we get back a renderProps variable and can render the route we were asked for. Then we can use React’s nifty renderToString to render the component out to HTML and pass it to the client in the react-view div we wrote earlier in:

<div id="react-view">${componentHTML}</div>

If we run npm start we should see the route being injected into the HTML at http://localhost:3000/:

Pretty, huh.

If you see a errors in the console about bundle.js not being found, Don’t Panic. This is because the client is trying to load bundle.js, but we haven’t configured Webpack’s entry point yet, so we get nothing.

It looks amazing, and I’ll bet your mind is already blown, but right now we just have a static page. In order to get that juicy React goodness all the cool kids are on about we’ll need to implement the router on the client too.

So go ahead and open client/index.jsx to write something like:

import React       from 'react';
import { render } from 'react-dom';
import { Router } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import routes from 'routes';
const history = createBrowserHistory();render(
<Router children={routes} history={history} />,
document.getElementById('react-view')
);

We tell React to inject the Router component into the react-view div, and give the Router some relevant props. The history object that we don’t see on the server is a necessary part of React Router’s configuration (when rendering it directly), and describes how URLs will look. We want nice, clean ones, so we use the HTML5 history API from createBrowserHistory, though for legacy browsers we could use createHashHistory and get /#/ style URLs.

Now we can start our app with npm run dev (instead of npm start) and Webpack will serve our client a bundle.js, so it can do it’s own routing from here-on-out. It still won’t look very interesting, but visiting http://localhost:8080/ should work without errors. Routing’s all set, and we’re ready for some Redux action.

Reduce, Reuse, Redux

A Redux set up looks very much like a Flux one except, as I mentioned earlier, reducers in place of stores. First we’ll write some simple actions to modify the Todo List:

shared/actions/TodoActions.js:

export function createTodo(text) {
return {
type: 'CREATE_TODO',
text,
date: Date.now()
}
}
export function editTodo(id, text) {
return {
type: 'EDIT_TODO',
id,
text,
date: Date.now()
};
}
export function deleteTodo(id) {
return {
type: 'DELETE_TODO',
id
};
}

As you can see, in Redux action creators are just functions that return consistently formatted objects. No magic here, we just need a reducer to handle them.

shared/reducers/TodoReducer.js:

import Immutable from 'immutable';const defaultState = new Immutable.List();export default function todoReducer(state = defaultState, action) {
switch(action.type) {
case 'CREATE_TODO':
return state.concat(action.text);
case 'EDIT_TODO':
return state.set(action.id, action.text);
case 'DELETE_TODO':
return state.delete(action.id);
default:
return state;
}
}

Again you’ll notice it’s extremely simple. Here we use an Immutable List object to store our state (though a more complex app would probably have a more elaborate structure), and return a newly transformed version depending on the action.

As you’ve probably gathered Redux isn’t very opinionated, and only has two expectations of its redeucers:

  1. The reducer has the signature (state, action) => newState.
  2. The reducer does not mutate the state it is given, but returns a new one

The latter makes it fit snugly with Immuatable.js, as we can see.

Here we use a switch statement catch actions for the sake of simplicity, but if you don’t like it feel free to write some simple reducer boilerplate to gain some abstraction.

So that Redux can pick up multiple reducers in the future, you’ll want also to have a reducers/index.js:

export { default as todos } from './TodoReducer';

Since we only have one reducer in this app, this isn’t that useful, but it’s a nice structure to have.

Annnnd… Action

All this talk of action and reduction is great, but our app still doesn’t know a thing about it! Time to change that.

We need to pass a Redux store instance through the component tree, so we can start dispatching some actions and tie the whole shebang together! The NPM package react-redux provides some useful addons for this, so that’s what we’ll use here.

server.jsx:

...
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import * as reducers from 'reducers';
app.use((req, res) => {
const location = createLocation(req.url);
const reducer = combineReducers(reducers);
const store = createStore(reducer);

match({ routes, location }, (err, redirectLocation, renderProps) => {
if (err) {
console.error(err);
return res.status(500).end('Internal server error');
}
if (!renderProps) return res.status(404).end('Not found.');

const InitialComponent = (
<Provider store={store}>
<RoutingContext {...renderProps} />
</Provider>
);
...

We create a new instance of a Redux store on every request, and inject that bad boy through the component tree (available as <component>.context.redux, if you ever want to access it directly) by wrapping the root component in Provider. We’ll also want to pass an initial state to the client, so it can hydrate it’s stores.

Just grab the state from Redux:

...
</Provider>
);
const initialState = store.getState();...

Then alter the HTML template to send it off to the client:

<title>Redux Demo</title>

<script type="application/javascript">
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
</script>

After this is added, we will have access to our state on the client under window.__INITIAL_STATE__. Pretty neat, huh. All we need to do is transform it back into Immutable.js collections, and pass it to Redux when we instantiate the new store:

client/index.jsx:

...
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import * as reducers from 'reducers';
import { fromJS } from 'immutable';
const history = createBrowserHistory();let initialState = window.__INITIAL_STATE__;// Transform into Immutable.js collections,
// but leave top level keys untouched for Redux
Object
 .keys(initialState)
 .forEach(key => {
initialState[key] = fromJS(initialState[key]);
});
const reducer = combineReducers(reducers);
const store = createStore(reducer, initialState);
render(
<Provider store={store}>
<Router children={routes} history={history} />
</Provider>,
document.getElementById('react-view')
);

This is identical to the server Redux initialization, except that we hydrate the store with the state passed from the server.

We’re actually nearing the finished app now, all we need is a couple of components to connect the dots.

Connecting the Dots

We’re going to use three components to display this information, which might seem a little like overkill (and it might be), but it demonstrates Redux’s distinction between ‘smart’ and ‘dumb’ components, which is important in larger applications.

Smart components are ones which subscribe to Redux’s store instance somehow (here using the @connector decorator syntax), and they can pass it down to their children. They can be at any point in the component tree, but you’ll find when developing more complex apps that they tend to bubble up to shallower layers naturally, as it makes sense for higher order components to hold application state and pass it on.

We’ll only use one here, at shared/components/Home.jsx:

import React                  from 'react';
import TodosView from 'components/TodosView';
import TodosForm from 'components/TodosForm';
import { bindActionCreators } from 'redux';
import * as TodoActions from 'actions/TodoActions';
import { connect } from 'react-redux';
@connect(state => ({ todos: state.todos }))export default class Home extends React.Component {
render() {
const { todos, dispatch } = this.props;

return (
<div id="todo-list">
<TodosView todos={todos}
{...bindActionCreators(TodoActions, dispatch)} />
<TodosForm
{...bindActionCreators(TodoActions, dispatch)} />
</div>
);
}
}

We’ll write the two dumb components next, but let’s take a look at what happens here first.

If you’re not familiar with decorators (the @connector section here), then it’s easiest to think of them as a replacement for what you would used to use a ‘mixin’ for. You may have actually seen them in other languages, like Python, for instance.

If not, in Javascript they are just functions that modify another given function (here a ‘class’) in some way. Notice how when defined like this (‘class leading’) they are attached to the following class definition, hence the lack of ;.

Redux’s @connect decorator wraps our class in another component ( <Connector>), giving it access to the requested parts of state as props, hence why we can use todos as we do. It also passes in Redux’s dispatch function which can be used to dispatch actions like so:

dispatch(actionCreator());

Finally we use redux’s bindActionCreators method to pass in… ermm… bound action creators. What this means is that, in the child components, we can just call the action creators directly, without wrapping them in a dispatch() call as above.

See exhibit A, components/TodosView.jsx:

import React from 'react';export default class TodosView extends React.Component {
handleDelete = (e) => {
const id = Number(e.target.dataset.id);

// Equivalent to `dispatch(deleteTodo())`
this.props.deleteTodo(id);
}
handleEdit = (e) => {
const id = Number(e.target.dataset.id);
const val = this.props.todos.get(id).text

// For cutting edge UX
let newVal = window.prompt('', val);
this.props.editTodo(id, newVal);
}

render() {
return (
<div id="todo-list">
{
this.props.todos.map( (todo, index) => {
return (
<div key={index}>
<span>{todo}</span>

<button data-id={index} onClick={this.handleDelete}>
X
</button>
<button data-id={index} onClick={this.handleEdit}>
Edit
</button>
</div>
);
})
}
</div>
);
}
}

Here we display each todo item in the store alongside edit and delete buttons, which are mapped to our pre-bound action creators.

Also note that we use arrow functions in the class definition, so that this will be bound to the class’ constructor (since arrow functions inherit the scope’s this). If we use ordinary ES6 class functions (like render), then we would need to bind them manually in the constructor, which is wearisome.

Note that you can also still use React.createClass, avoid this problem, and get mixins, though I still prefer to use straight ES6 classes for cleanliness and consistency.

Finally we want to define components/TodosForm.jsx:

import React from 'react';export default class TodosForm extends React.Component {
handleSubmit = () => {
let node = this.refs['todo-input'];

this.props.createTodo(node.value);

node.value = '';
}

render() {
return (
<div id="todo-form">
<input type="text" placeholder="type todo" ref="todo-input" />
<input type="submit" value="OK!" onClick={this.handleSubmit} />
</div>
);
}
}

This is also a ‘dumb’ component, and just lets the user add a todo to the store.

Now all we need is to define the route in our shared/routes.jsx file:

...
import Home from 'components/Home';
export default (
<Route name="app" component={App} path="/">
<Route component={Home} path="home" />
</Route>
);
If you’re telling yourself Evernote is better, you’re lying

Et voilà! You should be able to visit http://localhost:8080/home to see a functioning app!

The final frontier: asynchronous actions

I know what you’re thinking.

It can’t be done.

But I promise you, it can.

Another great feature of Redux is the ability to specify dispatcher middleware, which allow you transform actions (asynchronously). As I’m sure you have noticed is a theme with Redux, they’re just functions with a restricted set of signatures.

We’re going to use some custom Redux middleware to help us keep our actions simple (and our action creators synchronous), whilst also giving us the` ability to use shiny and delicious ES6 promises.

shared/lib/promiseMiddleware.js (modified from here):

export default function promiseMiddleware() {
return next => action => {
const { promise, type, ...rest } = action;

if (!promise) return next(action);

const SUCCESS = type;
const REQUEST = type + '_REQUEST';
const FAILURE = type + '_FAILURE';
next({ ...rest, type: REQUEST }); return promise
 .then(res => {
next({ ...rest, res, type: SUCCESS });

return true;
})
 .catch(error => {
next({ ...rest, error, type: FAILURE });

// Another benefit is being able to log all failures here
console.log(error);
return false;
});
};
}

This means that we can just define a promise key on our actions and have them automatically resolved and rejected. We can also optionally listen in the reducers for auto-generated <TYPE>_REQUEST and <TYPE>_FAILURE if we care about mutating state along the way.

All we need to do to use it is change a couple of lines in client/index.jsx and server.jsx like so:

...
import { applyMiddleware } from 'redux';
import promiseMiddleware from 'lib/promiseMiddleware';
...
const store = applyMiddleware(promiseMiddleware)(createStore)(reducer);

Making sure to pass in the initialState alongside reducer on the client.

And now we can write a magical new createTodo action creator, for instance:

import request from 'axios';const BACKEND_URL = 'https://webtask.it.auth0.com/api/run/wt-milomord-gmail_com-0/redux-tutorial-backend?webtask_no_cache=1';export function createTodo(text) {
return {
type: 'CREATE_TODO',
promise: request.post(BACKEND_URL, { text })
}
}

After a little change to the reducer:

return state.concat(action.res.data.text);
Two words: Ta. Da.

Todos will now save to my external database (though after 50 it nukes them all… So maybe don’t use it full-time). If we want to load them on application startup we can just add a getTodos action creator:

export function getTodos() {
return {
type: 'GET_TODOS',
promise: request.get(BACKEND_URL)
}
}

Catch it in the reducer:

case 'GET_TODOS':
return state.concat(action.res.data);

And we can call it when the TodosView component mounts:

componentDidMount() {
this.props.getTodos();
}

Since the middleware also fires action for the initial request and possible failure, you can see how we could also catch those in the reducers and update the app state accordingly, for loading or error states respectively.

Wait… Didn’t we break state rehydration?

Yes. Let’s fix it.

The problem is that we added async actions, but don’t wait for them to complete on the server before sending out state to the client. You might think this doesn’t matter much, since we can just display a loading screen on the client. Better than hanging on the server right?

Well, it depends. In fact, one major benefit of server-side rendering is that we can guarantee that we have a good connection to our backend (they could easily be in the same datacenter!). If a user tries to load your site on a dodgy mobile connection, for instance, it’s far better that they wait for your server to bundle the initial data and send it to them than wait on a bunch of different resources themselves.

Solving this problem with the current set up isn’t especially hard. You could do it a few ways, but the way I’ve been doing it so far is as follows:

We define what data the given component needs as an array of action creators to be fired. We can use a static on the class with something like:

static needs = [
TodoActions.getTodos
]

Then have a function which promises to gather all the data and dispatch it (shared/lib/fetchComponentData.js):

export default function fetchComponentData(dispatch, components, params) {
const needs = components.reduce( (prev, current) => {
return (current.needs || [])
 .concat((current.WrappedComponent ? current.WrappedComponent.needs : []) || [])
 .concat(prev);
}, []);

const promises = needs.map(need => dispatch(need(params)));
return Promise.all(promises);
}

Note that we also need to check a WrappedComponent key, since aforementioned ‘smart’ components will be wrapped in a Connector component.

Now we configure the server to only reply once it has the necessary data:

...
import fetchComponentData from 'lib/fetchComponentData';
match({ routes, location }, (err, redirectLocation, renderProps) => {
if (err) {
console.error(err);
return res.status(500).end('Internal server error.');
}
if (!renderProps) return res.status(404).end('Not found.'); function renderView() {
// ... Rest of the old code goes here still
return HTML;
}

fetchComponentData(store.dispatch, renderProps.components, renderProps.params)
 .then(renderView)
 .then(html => res.end(html))
 .catch(err => res.end(err.message));
});
Breathtaking.

Make sure to remove the action creator from componentDidMount to avoid duplicating the call, and you will need to re-run npm run dev to pickup changes in the server code.

What have we learned?

It’s a wonderful world.

There is of course always more that can be done. In an application with more routes, for example, you’ll want to use React Router’s onEnter handler to load a component’s needs (there’s an example here), and hook up other actions to an async API.

In spite of all this I hope you’ve enjoyed yourself on your epic quest to a more functional future. If you have any problems or suggestions for the article, make sure to give me a buzz either here or on Twitter.

You can also check out the finished version of what’s here on Github, as well as a version with more up to date dependencies here, and find out more about Redux here.

--

--