You may not need to thunk

A very common pattern in Redux is to use things called a Thunks, which are a way of wrapping up certain logic of a subroutine in a single function. In the context of Redux, this means taking a set of asynchronous or state-dependent action dispatches and wrapping them in a single action creator.

Let’s see an example: we’re going to have a button that dispatches an action and creates a new notification to show in a different part of the UI.

In the above, we’re using props.dispatch and creating the action objects directly, rather than action creators which are bound by react-redux. We could of course use those as well:

These are effectively the same, it just changes some of the boilerplate to allow you to use actions via “creator” functions instead of directly dispatching objects, which helps separate concerns and reduce code duplication.

Now, you may look at Example2.js above, and it may look a little different to your application code: Here I’m doing everything in one file, but in a real-world application, you’d likely separate your components and action creators out. For the context of this article, the separation of files doesn’t matter.

The example above is virtually the exact same one as linked in the redux-thunk documentation. If we were to do the above using redux-thunk, then it’d look something like this:

Just as before, show and hide are action creators: they encapsulate the logic of showing or hiding a notification, returning an object that describes the action we’re about to perform.

Then we have showTimed which is different; Rather than returning an object, it returns a function, which seems strange, let me explain:

This function doesn’t actually reach your reducers. Instead using some logic in between the dispatch in your component and receiving the action in your reducers, there’s some logic which is called. This is known as as Middleware.

In this case, the middleware evaluates the function and passes the result on to your reducers, also providing access to a dispatch method (pretty much the same one we saw before when doing this.props.dispatch). This piece of middleware is known as redux-thunk.

Redux-Thunk has become a cargo-cult within the Redux ecosystem: it is extremely rare not to see this middleware present in a Redux powered application. The most common way in which it’s used is to handle promises and complex asynchronous logic, this usage often results in the duplication code between action creators.

With this middleware we can perform all sorts of asynchronous logic in our action creators, dispatch multiple actions at the appropriate times from a single action creator, gain access to the current state of the store before dispatching an action, and also inject in an additional argument from a central place (e.g., an API module).

For example, we can setup redux-thunk to allow us to have an action creator such as the following:

This is a really powerful thing, suddenly rather than our action creators being just functions directly returning plain objects, they can be so much more.

However, with great power, comes great responsibility.

By using thunked action creators, we start passing around functions throughout our application, which means that our actions can no longer easily be serialised, which impedes the ability to log the actions and complicates debugging, to paraphrase Mark Erikson:

The idea is that we aren’t seeing what logic that led up to the action being dispatched, as opposed to dispatching an action that acts as a “signal” to kick off the more complex logic.

By not having this initial action logged, we don’t know why we started to do something.

It also means that our action creators can easily contain logic that perhaps shouldn’t be there. For example, often you’ll see projects that have action creators which look something like the following:

This is all well and good, however, most likely your application talks to an API at multiple points, across multiple files, which means every action creator is duplication the logic to perform API requests and connect them to your reducers to persistent different states in the store.

What we can do instead is create our own middleware to contain this logic; we could dispatch an action such as the following:

Which in a piece of custom middleware, is transformed into a different set of actions. This centralises all the logic for talking to the reddit API in our application, and gives us actions which describe what we’re wanting to do, rather than doing a complicated flow of logic.

Personally, I like to think of action creators as creating descriptions of work to be done, or descriptions of changes in the state of my application. Then again, I’m also a very big fan of concepts such as event-sourcing and CQRS.

A more complicated version of the custom middleware approach was used in the IDAGIO web application codebase, where the middleware performed all manner of operations, such as:

  • building URLs for API requests based on arguments
  • caching data, to prevent re-fetching the same data
  • translating API responses (our original API was in german, but we wanted all our application code to be in english, such that we could more easily grow our team).
  • handling errors consistently
  • and, unifying how we deal with asynchronous state in our reducers (hint: we used as status property on our actions, rather than multiple action types)

This all isn’t to say that redux-thunk isn’t a great tool, but it’s important to know when to use it, and when to use another strategy. You could also use patterns like redux-saga or redux-loop, it’s all a matter of personal preference.

Often when I see redux-thunk being used, I think what people actually want is a way to fetch the current state of the store, from their action creators, and then to combine that with some custom middleware for communicating with an API or other service that they depend upon.

The writing of this article was supported by donations to Em Smith, you can also hire her for code reviews, architect feedback, training. She’s even available for both contract and permanent hire.

This article also may need rewording at some point in the future, feel free to send me comments if you think there’s a way to communicate this article in a clearer fashion.