Enter Your Email Address to Watch This Lesson

Your link to unlock this lesson will be sent to this email address.

Unlock this lesson and all 827 of the free egghead.io lessons, plus get JavaScript content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

Just one more step!

Check your inbox for an email from us and click link to unlock your lesson.



Redux: Wrapping dispatch() to Recognize Promises

6:29 JavaScript lesson by

We will learn how to teach dispatch() to recognize Promises so that we can move the async logic out of the components into asynchronous action creators.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

We will learn how to teach dispatch() to recognize Promises so that we can move the async logic out of the components into asynchronous action creators.

Avatar
Howon

Couple of questions :)
1. when you call receiveTodos(..), how come you don't have to wrap dispatch(receiveTodos(...))?
2. when you do then(rawDispatch), does it do rawDispatch(response) when the promise is ready?

Avatar
Jiaming
  1. when you call receiveTodos(..), how come you don't have to wrap dispatch(receiveTodos(...))?

Because he passed the whole actions object into the connect function as the second argument. Since he imported the whole actions namespace into the actions object by import * as actions, the second argument, which is mapDispatchToProps, will do the dispatch wrapping for all of the exported functions in actions/index.js.

  1. when you do then(rawDispatch), does it do rawDispatch(response) when the promise is ready?

I also have problem understanding this one.

In reply to Howon
Avatar
Jiaming
  1. when you do then(rawDispatch), does it do rawDispatch(response) when the promise is ready?

I've figured out how this one works. When the fetchTodos actions is dispatched, it would wait for the response and build a receiveTodos action and return it as a promise. Since it's a promise, the addPromiseSupportToDispatch would chain the then(rawDispatch) at the end of it. Therefore, the rawDispatch would use the receivedTodos action returned by the previous promise as its argument and dispatch the receiveTodos action to the store.

In reply to Howon
Avatar
Ningze

For question No.1 : You don't need to call dispatch(receiveTodos(...)) because receiveTodos(...) is called in the first then() of Promise, and the returned value (which is an action) of the method in the first then() will be automatically fed into the input parameter of the method in the second then(), which is the rawDispatch.

For question No.2: No, it will do rawDispatch(action), not the response, because the response will be converted into an action by calling receiveTodos in the first then(), see the answer to question No.1

I think you are not quite clear about how Promise works. Your questions have nothing to do with redux or react.

In reply to Howon

The receiveTodos action creator is not very useful by itself because anytime we call it, we want to fetch the todos first. Also, they accept the same argument so it would be great if we could group this code into a single action creator.

VisibleTodoList.js

fetchData() {
  const { filter, receiveTodos } = this.props;
  fetchTodos(filter).then(todos =>
    receiveTodos(filter, todos)
  );
}

I'm opening the file where I define the action creators and I'm adding a new import there. It will make all functions in the API module available on the API namespace import object.

index.js

import * as api from '../api';

I'm adding a special kind of action creator that I'm going to call an asynchronous action creator. It takes filter as an argument and it calls the API 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.

index.js

export const fetchTodos = (filter) =>
  api.fetchTodos(filter).then(response =>
    receiveTodos(filter, response)
  );

ReceiveTodos return an action object synchronously but fetchTodos returns a promise that resolves through the action object. I can stop exporting receiveTodos because I'll change the components to use fetchTodos directly.

I'm going back to my component file and I can use the fetchTodos prop injected by connect() which corresponds to the new asynchronous fetchTodos action creator.

VisibleTodoList.js

fetchData() {
  const { filter, fetchTodos } = this.props;
  fetchTodos(filter);
}

I'm removing the direct import of fetchTodos function from API because from now on I'll be using the fetchTodos action creator, which is injected into the props by connect().

Let's take another look at what happens in the action creators. 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 dispatch in plain objects rather than promises. We can teach it to recognize promises by using the same trick that we use for addLoggingToDispatch.

addLoggingToDispatch is the function we wrote before that creates the dispatch from the store and returns a new version of dispatch that logs every action and the state. In a similar fashion, I can create a function called addPromiseSupportToDispatch that takes the store and returns a version of dispatch that supports promises.

configureStore.js

const addPromiseSupportToDispatch = (store) => {

}

First, we will write 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.

configureStore.js

const addPromiseSupportToDispatch = (store) => {
  const rawDispatch = store.dispatch;
  return (action) => {

  };
}

We don't know if the action is a real action or a promise of action, which check if it has a .then method that is a function, which is a way to tell if something is a promise. If it is a promise, we wait for it to resolve to an action object that we pass through rawDispatch. Otherwise, we'll just call rawDispatch right away with the action object we received.

configureStore.js

const addPromiseSupportToDispatch = (store) => {
  const rawDispatch = store.dispatch;
  return (action) => {
    if (typeof action.then === 'function') {
      return action.then(rawDispatch);
    }
  };
}

Now, we can dispatch both actions and promises that resolve to actions. Finally, I need to use the function I just wrote to write the dispatch function one more time before returning the store to the app.

configureStore.js

if (process.env.NODE_ENV !== 'production') {
  store.dispatch = addLoggingToDispatch(store);
}

store.dipatch = addLoggingToDispatch(store);

If I run the app now, I will still see the receiveTodos action being dispatch when the response is ready. However, the component uses a more convenient API that encapsulates the asynchronous logic in the asynchronous action creator.

[Async Action Creator](../images/javascript-redux-wrapping-dispatch-to-recognize-promises-async-action-creator.png)

One thing to remember is that the order in which we override the dispatch function is important. If we change it, the action will first be printed and then the promise will be processed so action type is undefined and we see the promise instead of the action, which is not very useful.

configureStore.js

store.dipatch = addLoggingToDispatch(store);

if (process.env.NODE_ENV !== 'production') {
  store.dispatch = addLoggingToDispatch(store);
}

[Async Action Creator](../images/javascript-redux-wrapping-dispatch-to-recognize-promises-action-type-undefined.png)

This is why I'm changing the auto back so that the promises are resolved before the action is locked. Let's recap how we added the promise support to the dispatch function.

We override store dispatch two times. Once to add the login and second time to add the promise support. The function that adds the promise support accepts the store an argument and it reaches the previous value of the dispatch function.

configureStore.JS

const addPromiseSupportToDispatch = (store) => {
  const rawDispatch = store.dispatch;
  return (action) => {
    if (typeof action.then === 'function') {
      return action.then(rawDispatch);
    }
    return rawDispatch(action);
  };
};

It returns a function that looks like a dispatch function because it accepts the action but if the action is not really an action but a promise, we're going to wait for that promise to resolve to the real action, which will pass through rawDispatch.

Finally, if the action is not a promise, we'll just dispatch it right away by calling rawDispatch. Let's see what happens when we try to dispatch the result of call and fetchTodos asynchronous action creator.

index.js

const receiveTodos = (filter, response) => ({
  type: 'RECEIVE_TODOS',
  filter,
  response,
});

export const fetchTodos = (filter) =>
  api.fetchTodos(filter).then(response =>
    receiveTodos(filter, response)
  );

First of all, it calls the fetchTodos function from the API module, which I import as a namespace import. It waits for the promise to resolve with the response and rather than return the response, it returns the result of calling receiveTodos action creator, which returns an action object.

This is why fetchTodos, itself, returns a promise that resolves through the action returned by receiveTodos. The wrapped dispatch function recognizes that this is a promise and not an action so it waits for the promise to resolve and passes it on through the rawDispatch function with dispatches the action object returned by receiveTodos.

configureStore.js

const addPromiseSupportToDispatch = (store) => {
  const rawDispatch = store.dispatch;
  return (action) => {
    if (typeof action.then === 'function') {
      return action.then(rawDispatch);
    }
    return rawDispatch(action);
  };
};

I remove the export from receiveTodos because it is only used as part of the asynchronous fetchTodos action creator. Being a named export, it becomes available as part of the actions object, which is the namespace import invisible TodoList.

VisibleTodoList.js

import * as actions from '../actions';

The actions object gets passed as a second argument through the connect function. This has me call these props fetchTodos from my component without thinking whether it is backed by a synchronous or an asynchronous action creator.

VisibleTodoList.js

VisibleTodoList = withRouter(connect(
  mapStateToProps,
  actions
)(VisibleTodoList));


HEY, QUICK QUESTION!
Joel's Head
Why are we asking?