An Introduction to React Redux (Part 3)

In the past two articles, I’ve looked at Redux as an implementation of the flux architecture and I’ve looked at wrapping React components so that they can take advantage of Redux without a lot of boilerplate code. However, I had noted one issue – async actions. Redux action creators need to return an object that describes the action. When you deal with async actions, the thing that is returned is generally a Promise these days. It most definitely is not an object that you can immediately dispatch.

Personally, I think this is a complete failure of Redux. Async operations are the cornerstone of server-client communication and the decision to defer this functionality to another library is really a problem. It’s a problem I can live with, but I shouldn’t have to.

Now that I’ve got that off my chest, let’s take a look at the facility that allows Redux to handle async functions – middleware. You’ve probably seen middleware before – ExpressJS, for example, relies on middleware to handle even the most basic functionality. The concept within Redux is the same thing. We insert a function in between the action and the reducer to transform the action into a form that the reducer can digest.

Even though my app does not make use of a major amount of server-side API right now, I want to solve this because I expect a large amount of communication between backend and frontend and I want to be able to handle the async requests the same way as the simpler synchronous requests.

Let’s take another look at my Promise-based action creator:

export function requestAuthInfo() {
    return (dispatch) => {
        let fetchOptions = {
            method: 'GET',
            credentials: 'include',
            cache: 'no-cache',
            mode: 'cors'
        };
        return fetch(`${baseUrl}/.auth/me`, fetchOptions)
        .then((response) => {
            if (!response.ok && response.status !== 401)
                throw new Error(`Invalid Response from ${baseUrl}/.auth/me:`, response);
            if (response.status === 401)
                return new Promise((resolve) => { resolve(false); });
            return response.json();
        })
        .then((response) => {
            if (!response)
                dispatch(receiveAnonymousAuth());
            else
                dispatch(receiveAuthenticatedAuth(response));
        })
        .catch((error) => {
            dispatch(receiveErrorCondition(error));
        });
    };
}

Theoretically, I can just create an action and then call the action, like this:

// Dispatch the initial action
let requestAction = requestAuthInfo();
requestAction(store.dispatch);

However, I normally use dispatch like this:

store.dispatch(requestAuthInfo());

Fortunately, you don’t actually have to write your own middleware. Today I’m going to introduce three pieces of middleware. We’ll get to the main attraction shortly, but first let’s take a look at how middleware works by implementing some dispatch logging. The redux-logger middleware will log the actions dispatched and provide information about the before and after state. To implement this, we alter our store:

import { createStore, applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import { requestAuthInfo } from './actions';
import reducer from './reducers';

const initialState = {
    phase: 'pending',
    user: null,
    error: null,
    leftMenuVisibility: false
};

const logger = createLogger();
const store = createStore(
    reducer, 
    initialState, 
    applyMiddleware(logger)
);

// Dispatch the initial action
let requestAction = requestAuthInfo();
requestAction(store.dispatch);

export default store;

UPDATE Based on advice from the Redux author – Dan Abramov – I’ve updated this sample.

The applyMiddleware() function takes a list of middleware functions – this is appended to the end of the createStore() paraeters so that the middleware is applied to the store. Note that the logger middleware must be placed last – otherwise you get wierd logs for actions that need to be transformed. If you run this project, you might get something like the following in the console window:

redux-logger-example

Each action is grouped and you see the before and after. This gives me a confidence that my components are doing the right thing – another ad-hoc test capability when doing development.

On to the meat of my problem though – how do I handle promises and functions in general? The answer is to use a thunk.

A what? Who makes up these terms? Seriously. Ok, let me break it down. A thunk is an action creator that returns a function instead of an object. My requestAuthInfo() action creator uses an arrow function which is basically a function, so I definitely need this. The middleware for this is called redux-thunk. That just handles the function mechanism though – I also need to handle the fact that the function returns a promise. That’s handled by redux-promise. Let me add all that to my redux/store.js:

import { createStore, applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import thunk from 'redux-thunk';
import reduxPromise from 'redux-promise';
import { requestAuthInfo } from './actions';
import reducer from './reducers';

const initialState = {
    phase: 'pending',
    user: null,
    error: null,
    leftMenuVisibility: false
};

const reduxLogger = createLogger();

const store = createStore(
    reducer,
    initialState,
    applyMiddleware(thunk, reduxPromise, reduxLogger)
);

// Dispatch the initial action
store.dispatch(requestAuthInfo());

export default store;

UPDATE Dan Abramov (who wrote redux) updated me to show the more JS-friendly way of doing this, and I love the new syntax. I have updated the syntax above to match what Dan is recommending now.

We need to order the middleware so that they are executed in the right order. The thunk handles the action creator that returns a function; the reduxPromise handles the case when the function returns a Promise, and finally, reduxLogger logs the before and after of the store state. I can now change the request for authentication information (line 24) to be a dispatched action.

Back on my soap-box. The redux-thunk and redux-promise libraries should just be incorporated into the createStore() call – they should not really be separate functions – async connectivity is core to most web apps.

It won’t stop me from using Redux. Remember when I started – my code was 214 lines long (not including the library code). This code is 167 lines long. In addition to the 25% reduction in actual code I have to write, there is also a reduction in boilerplate code that I have to write to use the store. That’s in addition to the fact that I don’t have to maintain my own library that doesn’t do half of what Redux does. All in all, I’d be happy if Redux stopped here. But it doesn’t.

As always, my code is available at my GitHub Repository. Since I updated the code after I published, please note that the code in the repo is different (see store.js)