Redux: Wrapping dispatch() to Log Actions

Dan Abramov
InstructorDan Abramov

Share this video with your friends

Send Tweet
Published 8 years ago
Updated 5 years ago

We will learn how centralized updates in Redux let us log every state change to the console along with the action that caused it.

[00:00] Now that my state shape is more complex, I want to override the store dispatch function to add some console logs there so that I can see how the state is affected by the actions.

[00:11] I'm creating the new function called add login to dispatch, that accepts store as an argument. It's going to wrap the dispatch provided by Redux, so it reads the raw dispatch from store.dispatch.

[00:26] It will return another function with the same signature as dispatch, which is a single action argument. Some browsers like Chrome support using console group API to group several log statements under a single title, and I'm passing the action type in order to group several logs under the action type.

[00:47] I will log the previous state before dispatching the action by calling store get state. Next, I will log the action itself so that I can see which action causes the change.

[01:01] To preserve the contract of the dispatch function exactly, I am declaring the return value, and I'm calling the raw dispatch function with the action. Now the calling code will not be able to distinguish between my function and the function provided by Redux, except that I also log some information.

[01:21] I want to log the next state as well, because after the dispatch is called, the state is guaranteed to be updated, so I can use store get state in order to receive the next state after the dispatch.

[01:34] Some browsers, such as Google Chrome, also offer an API to style console logs, and I'm going to add some colors to my logs. I'm painting the previous state gray. The action is blue, and the next state is green.

[01:50] A few final touches. Since the console group API is not available in all browsers, if it's not supported, I'll just return the raw dispatch as is.

[02:00] Finally, it's not a good idea to log everything in production, so I'll add a gate saying that if process and node [inaudible] is not production, then I'm going to run this code. Otherwise, I'm just going to leave the store as is.

[02:17] Let's have a look at how our app runs with the wrapped dispatch function. Now, every time I dispatch an action such as adding a todo, I can see it in the log. In the previous state, I can see that there are no IDs, and the ById look-up table is empty.

[02:35] Then I see the action with the type add todo, and the ID and text of the todo. Finally, in the state after dispatching the action, I can see both the ID in all IDs and the corresponding todo in the ById mapping.

[02:51] The only way to change the Redux store state is by dispatching an action, and now we log every action along with the state before and after it, so it's really easy to find, if there is a mistake in the state, which action caused it.

[03:07] Let's recap how this works. I added a new function that takes the store, grabs dispatch from it, and if your browser doesn't support the console group API, it'll just return the original dispatch function.

[03:21] Otherwise, I will return a new function that looks like dispatch. It accepts the action argument, and it even calls the original dispatch function inside. However, it also places a few logs into a group with the action type.

[03:36] I log the previous state by calling store get state before dispatching. I also log the action that caused the change, and finally, because I know that the store dispatch function is synchronous, I can call store get state again right after it to log the next state of the store.

[03:56] Finally, to keep complete compatibility with the underlying dispatch function, I return whatever it returned. Usually, this is the action object.

[04:05] My function returns a wrapped dispatch function, so I will use it to return value to override the store dispatch method. I don't want to run this code in production, so wrap it in an environment check.

[04:19] This check by itself is not enough, and it will only work correctly if in the production build you use Define plugin for Webpack or [inaudible] transform for Browserify.

~ 7 years ago

Regarding the very last utterance of the video, transcribed like this: "or [inaudible 4:29] transform for Browserify". If anyone is curious I think he's saying "envify". I.e.,

Jose Hernandez
Jose Hernandez
~ 5 years ago

Nowadays it's better to just use Redux DevTools.

J. Matthew
J. Matthew
~ 4 years ago

I was already using a prepackaged version of this logger in my project, so it's great fun to see how it actually works. This is an excellent example of enhancing an existing function in a very clean way.

(For those interested in functional programming techniques, it's also an excellent example of a higher-order function, i.e. one that uses closure and returns a function.)

Brendan Whiting
Brendan Whiting
~ 4 years ago

I'm not sure why we needed this allIds list. Can't we just Object.keys() the todos object if we need the ids?