22. Dispatching Actions Asynchronously with Thunks
To show the loading indicator in our current implementation, we dispatch the requestTodos
action before we fetch the todos with fetchTodos
. It would be great if we could make requestTodos
dispatch automatically when we fetch the todos because we never want them to fire separately.
VisibleTodoList.js
Before
// inside of `VisibleTodoList`
fetchData() {
const { filter, fetchTodos, requestTodos } = this.props;
requestTodos(filter);
fetchTodos(filter);
}
To start, we will remove the explicit requestTodos
dispatch from the component. Now we no longer need to export
the requestTodos
action creator inside of actions/index.js
(but don't erase the code for it).
Our goal is to dispatch requestTodos()
when we start fetching, and receiveTodos()
when they finish fetching, but our fetchTodos
action creator only resolves through the receiveTodos
action.
Current fetchTodos
Action Creator
export const fetchTodos = (filter) =>
api.fetchTodos(filter).then(response =>
receiveTodos(filter, response)
);
An action Promise resolves through to a single action at the end, but we want an abstraction that represents multiple actions dispatched over a period of time. This is why rather than returning a Promise, I want to return a function that accepts a dispatch callback argument.
This change lets us call dispatch()
as many times as we want at any point of time during the async operation. We can dispatch the requestTodos
action in the beginning, then when the Promise resolves we can explicitly dispatch another receiveTodos
action.
Updated fetchTodos
export const fetchTodos = (filter) => (dispatch) => {
dispatch(requestTodos(filter));
return api.fetchTodos(filter).then(response => {
dispatch(receiveTodos(filter, response));
});
};
This is more typing than returning a Promise, but it gives us more flexibility. Since a Promise can only express one async value, fetchTodos
now returns a function with a callback argument so that it can call it multiple times during the async operation.
Introducing Thunks
Functions returned from other functions like in fetchTodos
are often called thunks. Now we're going to implement a middleware to support using thunks in our code.
Updating configureStore.js
Inside of configureStore.js
we'll remove the redux-promise
middleware and replace it with the thunk
middleware we'll write now.
The thunk
middleware supports the dispatching of thunks. It takes the store
, the next middleware (next
), and the action
as arguments, just like any other middleware.
If action
is a function instead of an action, we're going to assume that this is a thunk that wants the dispatch
function to be injected into it. In this case, we'll call the action with store.dispatch
.
Otherwise, we just return the result of passing the action to the next middleware in chain.
thunk
Middleware in configureStore.js
const thunk = (store) => (next) => (action) =>
typeof action === 'function' ?
action(store.dispatch) :
next(action);
As a final step, we need to add our new thunk
middleware to the array of middlewares inside configureStore
so that it gets applied to the store.
const configureStore = () => {
const middlewares = [thunk];
// rest of configureStore