The benefit/purpose of the redux-thunk middleware was pretty hard to understand when I was first going through the redux docs, so I thought that writing some things down about it would be a good idea.

The goal of this post is to help better explain why redux-thunk exists, not how to use it. There is a ton of material out there that covers implementation details (I’ll link to a few below). Also, this post will probably make most sense if you have background knowledge on react and redux.

Anyways, let’s get into it.

As a general overview, redux-thunk is middleware designed to help make using asynchronous actions easier. Below is an example of an asynchronous action:

dispatch({
  type: START_NETWORK_REQUEST,
  payload: ACTION_PAYLOAD,
});

setTimeout(() => {
  dispatch({
    type: END_NETWORK_REQUEST,
  });
}, 5000);

Here, the setTimeout is used to mimic the delay that might occur during an async process (like a network call).

So every time we wanted to perform the above action, we’d need to copy and paste the above code. Of course, we can avoid repeating ourselves by wrapping the code in a function:

const emitAsyncAction = function(ACTION_PAYLOAD) {
  dispatch({
    type: START_NETWORK_REQUEST,
    payload: ACTION_PAYLOAD,
  });

  setTimeout(() => {
    dispatch({
      type: END_NETWORK_REQUEST,
    });
  }, 5000);
}

// Usage within a component.
emitAsyncAction(payload);

This looks alright but we will probably not have access to the dispatch function where emitAsyncAction is defined. We could somehow get around this if we have a singleton store but that introduces more complications if we also want to get server side rendering to work[1].

So the easiest approach is to just have dispatch be passed into emitAsyncAction:

const emitAsyncAction = function(dispatch, ACTION_PAYLOAD) {
  dispatch({
    type: START_NETWORK_REQUEST,
    payload: ACTION_PAYLOAD,
  });

  setTimeout(() => {
    dispatch({
      type: END_NETWORK_REQUEST,
    });
  }, 5000);
}

// Usage within a component.
emitAsyncAction(this.props.dispatch, payload);

This works and is pretty reasonable. However, some people might prefer not having to pass dispatch to emitAsyncAction. This is where redux-thunk comes in. The middleware allows dispatch to accept functions instead of just action objects.

After refactoring our code a little bit, we have:

const asyncActionCreator = function(ACTION_PAYLOAD) {
  return function() {
    dispatch({
      type: START_NETWORK_REQUEST,
      payload: ACTION_PAYLOAD,
    });

    setTimeout(() => {
      dispatch({
        type: END_NETWORK_REQUEST,
      });
    }, 5000);
  };
}

// Usage within a component.
this.props.dispatch(asyncActionCreator(payload));

Instead of having a function called emitAsyncAction, we have asyncActionCreator. Usually an “action” is just an object but we can think of the function returned by asyncActionCreator to be an action as well[2]. So when we dispatch our async action, redux/redux-thunk will execute the function in a context where dispatch is available.

And that’s it. redux-thunk is basically a form of syntactic sugar to prevent having to pass dispatch around. To me, the biggest aesthetic benefit is that dispatching asynchronous and synchronous actions look the same with redux-thunk:

const asyncActionCreator = function(ACTION_PAYLOAD) {
  return function() {
    ...
  };
}

const syncActionCreator = function(ACTION_PAYLOAD) {
  return {
    ...
  };
}

// Usage within a component.
this.props.dispatch(asyncActionCreator(payload));
this.props.dispatch(syncActionCreator(payload));

There are some other posts out there that help explain why redux-thunk exists, most notably this stackoverflow answer. Definitely check it out.

If you want to learn how to use redux-thunk, check out some of these links:


[1] http://redux.js.org/docs/recipes/ServerRendering.html
[2] I mean, technically, a JS function is an object but you know what I mean.