Simplifying Redux
By: Einat Bertenthal
Tags: JavaScript, Middleware, Web Development, React, Redux

Let’s look at the API reference for Redux. The first item on the list there is createStore, which we’re going to implement right now!

createStore receives 3 parameters:

reducer initial state enhancers (for simplicity we will use only middlewares)

createStore returns an object with the following functions:

getState dispatch subscribe

First, we’ll create the wireframe for createStore :

OK, now that we have the wireframe, we can start implementing it step by step.

First of all, we need to add a local variable named state that is initialized with the initialState passed to the createStore function. Since getState just returns the state we can easily implement it:

let state = initialState;

return {

getState: () => state,

...

}

Another simple implementation is subscribe :

subscribe is a function that receives a listener and returns an unsubscribe function which will remove the listener so that it is no longer called on store changes.

const listeners = [];

return {

subscribe: (listener) => {

listeners.push(listener);

return () => {

listeners.splice(listeners.indexOf(listener), 1);}

}

},

...

}

Before we proceed to dispatch let’s implement the last step of our Redux implementation, which is evaluating the reducer, calling the listeners, and returning the new state:

let state = initialState;

const listeners = []; let dispatchAction = (action) => {

state = reducer(state, action);

listeners.forEach(listener => listener());

return state;

}

Now, dispatch is a function that receives an action and triggers state changes to the store. Basically, it calls the last step with the action it receives:

return {

dispatch: (action) => dispatchAction(action),

...

}

In the above implementation we used only the reducer to trigger state changes to the store, but what about our middlewares? What about all the side effects we want to execute before Redux changes the state? Before we do that let’s explore the structure of the middleware:

/* A */ (middlewareAPI) => /* B */ (next) => /* C */ (action) => {

....

return next(action);

}

A middleware is a function that returns another function that returns another function, and so on… But what’s interesting is that function B of the current middleware, receives a reference to function C of the next middleware in the array. This composition enables us to call the next middleware from the current middleware and so on until we cover all of them. Only then we can continue to the reducer evaluation. What we have here is actually a chain of middlewares!

Let’s dive into the most interesting part of our implementation:

let state = initialState;

const listeners = []; let finalMiddleware = (action) => {

state = reducer(state, action);

listeners.forEach(listener => listener());

return state;

} const middlewareAPI = {

getState: () => state,

dispatch: (action) => finalMiddleware(action)

} const newMiddleWares = middlewares.map(middleware => middleware(middlewareAPI)); for(let i = newMiddleWares.length - 1; i >= 0; i--) {

finalMiddleware = newMiddleWares[i](finalMiddleware);

}

Note that we changed dispatchAction to finalMiddleware . Don’t worry, it will all make sense when we finish everything!

First, we evaluate each middleware by calling it with the middleware API. Now newMiddleWares is a collection of functions with the following structure:

(next) => (action) => {

....

return next(action);

}

At the last step, we go over the new middlewares in reverse where we evaluate each middleware with the previously executed value. This part is a little bit confusing so let’s start with the last middleware.

The last middleware receives the first value of finalMiddleware , which is to evaluate the reducer, and it returns the last function in the middleware’s structure. finalMiddleware is now updated with that function.

The middleware before it will receive that function and return its last function in the structure. We repeat this process until we reach the first middleware.

At the end, the finalMiddleware will be the first middleware in the collection, so when we invoke dispatch it will call the first middleware.