Redux: Wrapping dispatch() to Recognize Promises

Dan Abramov
InstructorDan Abramov

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 3 years ago

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.

[00:01] The receive todo 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.

[00:17] 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.

[00:31] I'm adding a special kind of action creator that I'm going to call in an asynchronous action creator. It takes filter as an argument and it calls the API fetch todos method with it. I'm using the promise the method to transform the result of the promise from the response to the action object generated by received todos given the filter and the response.

[00:58] Received todos return an action object synchronously but fetch todos returns a promise that resolves through the action object. I can stop exporting receive todos because I'll change the components to sue fetch todos directly.

[01:15] I'm going back to my component file and I can use the fetch todos prop injected by connect which corresponds to the new asynchronous fetch todos action creator. I'm removing the direct import of fetch todos function from API because from now on I'll be using the fetch todos action creator, which is injected into the props by connect.

[01:40] Let's take another look at what happens in the action creators. The fetch todos action creator calls the fetch todos function from the API but then it transforms its result into a Redux action generated by receive todos.

[01:57] 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 login every dispatch.

[02:11] A login into dispatch 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 at promise support the dispatch that takes the store and returns a version of dispatch that supports promises.

[02:35] First, we will write the raw dispatch 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.

[02:51] We don't know if the action is a real action or a promise of action, which hack 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 raw dispatch.

[03:11] Otherwise, we'll just call raw dispatch right away with the action object we received. 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.

[03:32] If I run the app now, I will still see the receive todos 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.

[03:49] 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.

[04:08] 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.

[04:20] 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.

[04:37] 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 raw dispatch.

[04:53] Finally, if the action is not a promise, we'll just dispatch it right away by calling raw dispatch. Let's see what happens when we try to dispatch the result of call and fetch todos asynchronous action creator.

[05:08] First of all, it calls the fetch todos 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 receive todos action creator, which returns an action object.

[05:31] This is why fetch todos, itself, returns a promise that resolves through the action returned by receive todos. The wrap 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 raw dispatch function with dispatches the action object returned by receive todos.

[05:56] I remove the export from receive todos because it is only used as part of the asynchronous fetch todos action creator. Being a named expert, it becomes available as part of the actions object, which is the namespace import invisible todo list.

[06:14] The actions object gets passed as a second argument through the connect function. This has me call these props fetch todos from my component without thinking whether it is backed by a synchronous and an asynchronous action creator.

~ 6 years ago

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?
~ 6 years ago
  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.

~ 6 years ago
  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.

~ 6 years ago

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.

J. Matthew
J. Matthew
~ 3 years ago

Nice questions, @Howon. I was wondering about the first one, myself! Coming from using Redux in Angular, I'm used to having to explicitly call dispatch(<action>). I think @Jiaming answered your questions well. I had forgotten the earlier video that talked about mapDispatchToProps and how you could pass an object instead, like this code does with the actions namespace. It's very clean but hides the actual dispatch, so that can be somewhat confusing.

@Jiaming also explained the mechanics of the second question well, but I just want to clarify the syntax further:
.then(rawDispatch) = .then(actionObject => rawDispatch(actionObject))
It's just a more compact way to write the code, which is good but does have the downside of being slightly more confusing, since you're not providing a name for what's being passed through from the .then.