Asynchronous actions in Redux
Delving into asynchronous actions is a case-in-point on the paradox of choice. There are so many options that it’s hard to know where to begin. That’s one reason why this post has taken longer than my previous React/Redux posts. Another is that I’ve been on vacation. :)
Also available are:
So there we have almost a dozen different ways to work with async actions. There are probably many additional approaches, and even these aren’t mutually exclusive — they can be used in combination.
Why async actions?
But let’s back up a bit. What’s the problem we’re trying to solve here?
In my previous post, I compared the way Redux works to a publish-subscribe message bus. You dispatch an action just like you would publish a message. You don’t worry who is going to handle it — or when. The message publisher continues on with its work, without waiting for a response.
As soon as we’re beyond Hello World, one of the very first things we need to do (in the vast majority of apps) is communicate with a remote API, and we don’t want to do that synchronously. We want to kick off a request, and some time later we want to handle the response or error that comes back.
Many Redux-less React tutorials, including the React docs, demonstrate making async API calls in the componentDidMount() function. You make the call, and in the callback function or when the Promise is resolved/rejected, you update the component’s local state appropriately. React then re-renders the component.
What do you do when you want to manage that state in Redux instead?
Redux middleware options
Fortunately, Redux provides a very nice way to extend its functionality: middleware. Redux middleware is conceptually the same as in Express and many other web server frameworks. You create a pipeline of middleware components, each of which has the opportunity to act on whatever is passing through the pipeline — in this case, Redux actions.
In short, you move the async API calls into your action creator functions. Your component continues to call an action creator, which then uses a Promise to postpone dispatching an action until the API call is complete. The dispatched action causes a reducer to update its state, which causes your component to re-render. You wind up with a single place to manage each async call.
You will also rather quickly wind up with a lot of boilerplate code in your action creators. There is a very common pattern for handling most API calls:
- Dispatch a PENDING action so your UI can show a spinner or some other indicator that a call is in progress.
- Make the call.
- In a callback or Promise resolve function, dispatch a SUCCEEDED action, so that your reducers can update their state.
- Similarly, in an error callback or Promise reject function, dispatch a FAILED action, so that your reducers can update their state.
The only differences across API calls are likely to be the request’s HTTP method, path, and body; as well as the type and data included in the action you dispatch on success or failure.
The real world example included in the Redux repo shows how to create middleware that handles this boilerplate for you. (If you like the way that example works, you can find it on npm as redux-api-middleware.)
A key step in using middleware to DRY up your code is to create a standard structure for your action objects. To be useful, middleware that handles multiple action types has to count on their having the same structure. I’m sure this will be no surprise: you can create your own standard or use one of several competing conventions that have arisen. Those conventions will be the topic of my next post.
So what do I use?
And now I’m at the end of my post on async actions without ever having said what approach I’m using. Well, for the moment, I’m using redux-thunk and my own middleware that is similar to redux-api-middleware. I’m about to add redux-promise. I like the look of redux-saga, but it’s fairly complex and I want to wait until I really have a need for it.
The short version is that, for me, the jury is still out on how to handle async actions. I’ll come back to this topic as I learn more.