Async Actions
Last updated
Last updated
In the , we built a simple todo application. It was fully synchronous. Every time an action was dispatched, the state was updated immediately.
In this guide, we will build a different, asynchronous application. It will use the Reddit API to show the current headlines for a selected subreddit. How does asynchronicity fit into Redux flow?
When you call an asynchronous API, there are two crucial moments in time: the moment you start the call, and the moment when you receive an answer (or a timeout).
Each of these two moments usually require a change in the application state; to do that, you need to dispatch normal actions that will be processed by reducers synchronously. Usually, for any API request you'll want to dispatch at least three different kinds of actions:
An action informing the reducers that the request began.
The reducers may handle this action by toggling an isFetching
flag in the state. This way the UI knows it's time to show a spinner.
An action informing the reducers that the request finished successfully.
The reducers may handle this action by merging the new data into the state they manage and resetting isFetching
. The UI would hide the spinner, and display the fetched data.
An action informing the reducers that the request failed.
The reducers may handle this action by resetting isFetching
. Additionally, some reducers may want to store the error message so the UI can display it.
You may use a dedicated status
field in your actions:
Or you can define separate types for them:
Whatever convention you choose, stick with it throughout the application. We'll use separate types in this tutorial.
Let's start by defining the several synchronous action types and action creators we need in our example app. Here, the user can select a subreddit to display:
actions.js
(Synchronous)They can also press a “refresh” button to update it:
These were the actions governed by the user interaction. We will also have another kind of action, governed by the network requests. We will see how to dispatch them later, but for now, we just want to define them.
When it's time to fetch the posts for some subreddit, we will dispatch a REQUEST_POSTS
action:
It is important for it to be separate from SELECT_SUBREDDIT
or INVALIDATE_SUBREDDIT
. While they may occur one after another, as the app grows more complex, you might want to fetch some data independently of the user action (for example, to prefetch the most popular subreddits, or to refresh stale data once in a while). You may also want to fetch in response to a route change, so it's not wise to couple fetching to some particular UI event early on.
Finally, when the network request comes through, we will dispatch RECEIVE_POSTS
:
This is all we need to know for now. The particular mechanism to dispatch these actions together with network requests will be discussed later.
Note on Error Handling
This part is often confusing to beginners, because it is not immediately clear what information describes the state of an asynchronous application, and how to organize it in a single tree.
We'll start with the most common use case: lists. Web applications often show lists of things. For example, a list of posts, or a list of friends. You'll need to figure out what sorts of lists your app can show. You want to store them separately in the state, because this way you can cache them and only fetch again if necessary.
Here's what the state shape for our “Reddit headlines” app might look like:
There are a few important bits here:
We store each subreddit's information separately so we can cache every subreddit. When the user switches between them the second time, the update will be instant, and we won't need to refetch unless we want to. Don't worry about all these items being in memory: unless you're dealing with tens of thousands of items, and your user rarely closes the tab, you won't need any sort of cleanup.
For every list of items, you'll want to store isFetching
to show a spinner, didInvalidate
so you can later toggle it when the data is stale, lastUpdated
so you know when it was fetched the last time, and the items
themselves. In a real app, you'll also want to store pagination state like fetchedPageCount
and nextPageUrl
.
Note on Nested EntitiesIn this example, we store the received items together with the pagination information. However, this approach won't work well if you have nested entities referencing each other, or if you let the user edit items. Imagine the user wants to edit a fetched post, but this post is duplicated in several places in the state tree. This would be really painful to implement.
In this guide, we won't normalize entities, but it's something you should consider for a more dynamic application.
Before going into the details of dispatching actions together with network requests, we will write the reducers for the actions we defined above.
Note on Reducer Composition
reducers.js
In this code, there are two interesting parts:
We use ES6 computed property syntax so we can update state[action.subreddit]
with Object.assign()
in a concise way. This:
is equivalent to this:
Remember that reducers are just functions, so you can use functional composition and higher-order functions as much as you feel comfortable.
When an action creator returns a function, that function will get executed by the Redux Thunk middleware. This function doesn't need to be pure; it is thus allowed to have side effects, including executing asynchronous API calls. The function can also dispatch actions—like those synchronous actions we defined earlier.
We can still define these special thunk action creators inside our actions.js
file:
actions.js
(Asynchronous)
Note onfetch
index.js
The nice thing about thunks is that they can dispatch results of each other:
actions.js
(with fetch
)This lets us write more sophisticated async control flow gradually, while the consuming code can stay pretty much the same:
index.js
Note about Server RenderingAsync action creators are especially convenient for server rendering. You can create a store, dispatch a single async action creator that dispatches other async action creators to fetch data for a whole section of your app, and only render after the Promise it returns, completes. Then your store will already be hydrated with the state you need before rendering.
It is up to you to try a few options, choose a convention you like, and follow it, whether with, or without the middleware.
Choosing whether to use a single action type with flags, or multiple action types, is up to you. It's a convention you need to decide with your team. Multiple types leave less room for a mistake, but this is not an issue if you generate action creators and reducers with a helper library like .
In a real app, you'd also want to dispatch an action on request failure. We won't implement error handling in this tutorial, but the shows one of the possible approaches.
Just like in the basic tutorial, you'll need to before rushing into the implementation. With asynchronous code, there is more state to take care of, so we need to think it through.
If you have nested entities, or if you let users edit received entities, you should keep them separately in the state as if it was a database. In pagination information, you would only refer to them by their IDs. This lets you always keep them up to date. The shows this approach, together with to normalize the nested API responses. With this approach, your state might look like this:
Here, we assume that you understand reducer composition with , as described in the section on the . If you don't, please .
We extracted posts(state, action)
that manages the state of a specific post list. This is just ! It is our choice how to split the reducer into smaller reducers, and in this case, we're delegating updating items inside an object to a posts
reducer. The goes even further, showing how to create a reducer factory for parameterized pagination reducers.
Finally, how do we use the synchronous action creators we together with network requests? The standard way to do it with Redux is to use the . It comes in a separate package called redux-thunk
. We'll explain how middleware works in general ; for now, there is just one important thing you need to know: by using this specific middleware, an action creator can return a function instead of an action object. This way, the action creator becomes a .
We use in the examples. It is a new API for making network requests that replaces XMLHttpRequest
for most common needs. Because most browsers don't yet support it natively, we suggest that you use library:
Internally, it uses on the client, and on the server, so you won't need to change API calls if you change your app to be .
Be aware that any fetch
polyfill assumes a polyfill is already present. The easiest way to ensure you have a Promise polyfill is to enable Babel's ES6 polyfill in your entry point before any other code runs:
How do we include the Redux Thunk middleware in the dispatch mechanism? We use the store enhancer from Redux, as shown below:
isn't the only way to orchestrate asynchronous actions in Redux:
You can use or to dispatch Promises instead of functions.
You can use to dispatch Observables.
You can use the middleware to build more complex asynchronous actions.
You can use the middleware to dispatch promise-based asynchronous actions.
You can even write a custom middleware to describe calls to your API, like the does.
Dispatching async actions is no different from dispatching synchronous actions, so we won't discuss this in detail. See for an introduction into using Redux from React components. See for the complete source code discussed in this example.
Read to recap how async actions fit into the Redux flow.