The receiveTodos
action creator is not very useful by itself because anytime we call it, we want to fetch the todos
first. Since fetchTodos
and receiveTodos
accept the same arguments, it would be great if we could group this code into a single action creator.
fetchData() {
const { filter, receiveTodos } = this.props;
fetchTodos(filter).then(todos =>
receiveTodos(filter, todos)
);
}
We'll get started by importing our fake API into our action creators file (src/actions/index.js
).
import * as api from '../api'
Now we'll add an asynchronous action creator called fetchTodos
. It takes filter
as an argument, and then it calls the API's fetchTodos
method with it.
I'm using the Promise then
method to transform the result of the Promise from the response
to the action object generated by receiveTodos
given the filter
and the response
.
export const fetchTodos = (filter) =>
api.fetchTodos(filter).then(response =>
receiveTodos(filter, response)
);
receiveTodos
returns an action object synchronously, but fetchTodos
returns a Promise that resolves through the action object.
Now can stop exporting receiveTodos
from our action creators because we can change the components to use fetchTodos
directly.
Back in our component file, we can use the fetchTodos
prop injected by connect
. This corresponds to the new asynchronous fetchTodos
action creator we just wrote.
We can remove import { fetchTodos } from '../api'
because from now on we will be using the fetchTodos
action creator, which is injected into the props by connect
.
fetchData() {
const { filter, fetchTodos } = this.props;
fetchTodos(filter);
}
The fetchTodos
action creator calls the fetchTodos
function from the API, but then it transforms its result into a Redux action generated by receiveTodos
.
However, by default, Redux only allows dispatching plain objects rather than Promises. We can teach it to recognize Promises by using the same trick that we used in addLoggingToDispatch()
inside of configureStore.js
(recall that the addLoggingToDispatch
function takes the dispatch
from the store
and returns a new version of dispatch
that logs every action and the state
).
Inside of configureStore.js
, we will create a function addPromiseSupport()
that takes the store
and returns a version of dispatch
that supports promises.
First, we will grab the rawDispatch
function at it is defined on the store
so that we can call it later. We return a function that has the same API as a dispatch function-- that is, it takes an action.
const addPromiseSupportToDispatch = (store) => {
const rawDispatch = store.dispatch;
return (action) => {
if (typeof action.then === 'function') {
return action.then(rawDispatch);
}
return rawDispatch(action);
};
};
Since we don't know if the action
is a real action or a promise of an action, we check if it has a then
method that is a function. If it does, then we know it is a promise. If the action is a promise, we wait for it to resolve to an action object that we will pass through rawDispatch
.
Otherwise, we'll just call rawDispatch
right away with the action
object we received.
Our new addPromiseSupportToDispatch
function allows us to dispatch both actions and promises that resolve to actions.
To finish up, we need to call our new function one more time before returning the store to the app.
// At the bottom of `configureStore.js`
const configureStore = () => {
const store = createStore(todoApp);
if (process.env.NODE_ENV !== 'production') {
store.dispatch = addLoggingToDispatch(store);
}
store.dispatch = addPromiseSupportToDispatch(store);
return store;
};
If we run the app now, we will still see the 'RECEIVE_TODOS'
action being dispatched when the response is ready. However, the component uses a more convenient API that encapsulates the asynchronous logic in the asynchronous action creator.
Remember that the order in which we override the dispatch
function inside of configureStore
is important.
If we change it so that we call addPromiseSupportToDispatch
before addLoggingToDispatch
, the action will first be printed and then the promise will be processed
This would give us an action type of undefined
and we see the Promise instead of the action, which is not very useful.