A well-known scientist once gave a public lecture on astronomy. He described how the earth orbits around the sun and how the sun, in turn, orbits around the center of a vast collection of stars called our galaxy. At the end of the lecture, a little old lady at the back of the room got up and said: ‘What you have told us is rubbish. The world is really a flat plate supported on the back of a giant tortoise.’ The scientist gave a superior smile before replying, ‘What is the tortoise standing on?’
‘You’re very clever, young man, very clever,’ said the old lady. ‘But it’s turtles all the way down!’
— Hawking, 1988
I previously wrote an article, You Aren’t Using Redux Middleware Enough. Afterwards, I got literally some retweets — my most successful article to date! I figured people must like middlewares as much as I do! And that’s because middlewares are such powerful concept, that can be so fundamental to Redux to the point that it’s middlewares all the way down!
We’ll start by creating a simple combineMiddlewares implementation. We’ll also create a bit of terminology. We’ll use the standard definition of middleware to mean a function with signature store => next => action, and we’ll call a propagation function this same function with store invoked (so a signature of next => action).
Combining middlewares is relatively straight forward, but it can be hard to wrap your head around it. We essentially want to have the next function of each middleware point to the the next middleware — except for the last middleware where next will be null.
We end up with a function that when invoked with an action will send that action to the first middleware, and when that middleware calls next(action), it’ll pass it on to the next, and so forth.
I spelled everything out to demonstrate terminology; however, this entire function could be combined in to just a single reduceRight call.
The result of combineMiddlewares when with a store is actually the store’s dispatch function. We can hook it up like below, and we’ll have our almost-complete Redux implementation.
Alright, we have no state, so this does absolutely nothing. Let’s add some state. This may surprise you — or maybe it won’t (the title really gives this one away) — but we’re going to use middleware to do this.
We’re going to make a reducerMiddleware that stores and updates state. It’ll update the state by forwarding the action provided to the middleware to the reducer. We can then then expose the state to the store by adding a getState function to the store.
To be consistent with Redux, This must be the last reducer in the chain. Because of this, we don’t call the next function because next is null (see combineMiddlewares).
Now we just have to link it up to the store, and then dispatch an init event to be consistent with Redux.
This actually works — go ahead, test it! That’s the the almost entirety of Redux, and we’re less than 30 lines in!
For the sake of completeness, we’ll add the store subscribe function. And of course, we’re going to use middleware. For this to really make sense, you’ll want to read my previous blog post, but the rest is simple.
There’s also an observer functionallity for Redux, but eh, you get the idea. Middleware etc.
So there you go: it’s middlewares all the way down. No state; just middlewares.
To take this to the extreme, you could view it that middlewares are the fundamental concept of Redux, and state is just a convention.
Worth noting that this is not how the actual Redux implementation works, but it could be.
Hope you enjoyed reading!
The code’s at https://github.com/jacobp100/middleware-all-the-way-down