Redux: The Middleware Chain

Dan Abramov
InstructorDan Abramov
Share this video with your friends

Social Share Links

Send Tweet

We will learn how we can generalize wrapping dispatch() for different purposes into a concept called “middleware” that is widely available in the Redux ecosystem.

[00:00] We have written two functions that wrap the dispatch function to add custom behavior. Let's take a closer look at how they work together.

[00:08] The final version of the dispatch function before we turn in the store is the result of calling add promise support to this patch.

[00:17] The function it returns acts like a normal dispatch function, but if it gets a promise, it waits for this promise to resolve and passes the result to raw dispatch, where raw dispatch is the previous value of store dispatch.

[00:33] For non-promises, it calls raw dispatch right away. Raw dispatch corresponds to the store dispatch at the time add promise support to dispatch was called.

[00:45] Store dispatch was reassigned earlier, so it's not completely fair to refer to it as raw dispatch. I'm renaming raw dispatch to next because this is the next dispatch function in the chain.

[01:00] It refers to store dispatch that was returned from add login to dispatch. Add login to dispatch also returns a function with the same API as the original dispatch function, but it logs the action type, the previous state, the action, and the next state along the way.

[01:21] It calls the raw dispatch with corresponds to store dispatch at the time add login to dispatch was called. In this case, this is the store dispatch provided by create store.

[01:34] However, it is entirely conceivable that we might want to override dispatch function before adding the login. For consistency, I'm going to rename raw dispatch to next, as well. It's just that in this particular case, next points to the original store dispatch.

[01:53] While this method of extending the store works, it's not really great at re-override the public API and replace it with custom functions. To get away from this pattern, I am declaring an array of what I'm calling middleware functions, which is just a fancy name I use for these functions I wrote, and I'm pushing add login to dispatch itself to an array, so this is an array of functions that will be applied later as a single step.

[02:22] I'm also adding add promise support to dispatch to the middleware array, and again, I don't need to apply it yet because I'll write a separate function that applies the middlewares.

[02:34] I will call this function grab dispatch with middlewares, and I'll pass this store as the first argument and an array of middlewares as the second argument.

[02:46] I'm adding this new function called grab dispatch with middlewares, and inside it I'm going to use the middlewares for each method. to run some code for every middleware.

[02:58] Specifically, I want to override this store dispatch function to point to the result of calling the middleware with the store as an argument.

[03:07] Inside the middleware functions themselves, there is a certain pattern that I have to repeat. I'm grabbing the value of store dispatch, and I'm storing it in a variable called next so that I can call it later.

[03:23] To make it a part of the middleware contract, I can make next an outside argument, just like the store before it and the action after it.

[03:34] This way, the middleware becomes a function that returns a function that returns a function, which is not very common in JavaScript, but is actually very common in functional programming languages, and this pattern is called Korean.

[03:48] Again, rather than take the next middleware from the store, I will make it intractable as an argument so that my function that calls the middlewares can choose which middleware to pass.

[04:04] Finally, store is not the only injected argument, so I also need to inject the next middleware, which is the previous value of store dispatch function.

[04:14] Now that middlewares are a first-class concept, I will rename add login to dispatch to just logger, and I will rename add promise support to dispatch to the promise middleware.

[04:31] The Korean style of function declaration can get very hard to read. However, we can use arrow functions and rely on the fact that arrow functions can have expressions as their bodies.

[04:46] It is still a function that returns a function returning a function, but it's much easier to read, and the mental model you can use for this is that this is just a function with several arguments that are applied as they become available.

[05:04] Finally, I'd like to fix the order in which middlewares are specified. They are currently specified in the order in which the dispatch function is overridden. However, it would be more natural to specify the order in which the action propagates through the middlewares.

[05:22] This is why I'm changing my middleware declaration to specify them in the order in which the action travels through them. Instead, I will grab dispatch with middlewares from right to left, so I clone the past array and I reverse it.

[05:41] Let's recap what we learned about middleware so far. Middleware can serve very different purposes. It might add some useful debugging information, such as the case with login middleware, or it might amend dispatch to understand something other than plain actions, which is the case with the promise middleware.

[06:02] We created a new function that takes the store and an array of middlewares, and does the dirty work of reassigning the store dispatch function. This lets me keep configure store a little bit more declarative, because I specified the middlewares in an array, and I call grab dispatch with middlewares to apply them.

[06:25] Let's recap how grab dispatch with middlewares works internally. The store object that the application uses contains a dispatch function. Grab dispatch with middlewares overrides it multiple times as it enumerates the middleware's array.

[06:42] However, it reverses the array before enumerating it, so the final value of store dispatch will correspond to the result of calling the first middleware in the array, which is the promise middleware.

[06:57] The promise middleware returns a dispatch function that understands promises, but to get it, we pass the store and the next dispatch function as carried arguments.

[07:12] The next dispatch function is the one that we got from the middleware that appeared earlier in the reverse array, or later in the original array, and this is the logger middleware.

[07:24] The logger middleware provided the dispatch function that logs the actions, but to get it, we passed the store and the next dispatch function to it, and it corresponded to the value store dispatch that was there before the middleware was even applied, and this is the original dispatch implementation from create store.

[07:49] The purpose of the middlewares is to replace the single dispatch function with a chain of composable dispatch functions which each can do something with an action.

[08:01] The promise middleware appears before the logger middleware in the chain, and this is why the next inside it corresponds to the dispatch function returned from the logger middleware.

[08:13] Logger middleware itself is the last middleware in chain, so its next function corresponds to the original store dispatch function, which is defined inside create store.

[08:26] Middleware is a powerful system that lets us put custom behavior before action reaches the reducers. People use middleware for different purposes, such as login, analytics, error-handling, asynchronous counterflow, and more.