Dissecting Redux

A breakdown of everyone’s favourite state container

Jan
8 min readApr 10, 2017

At Sepior we use Redux as our state management container. It’s a small neat library that simplifies a lot of data flow woes often found in interaction heavy web-apps.

Whether or not you are a fan of the technology, it is a good exercise to at least understand how Redux works. The core of the library is about 1000 lines of code, the core parts, with comments and validation removed is much smaller.

I will be taking a look at the source code of Redux, and looking only the at parts that matter, and I will explain the design decisions/patterns and the consequence they have. I assume that the reader has an understanding of how Redux works already and has some basic understanding of functional programming.

Please note, some parts of the source are edited to better fit into a 75 column line width.

This article was written for Redux 3.6.0.

Redux takes its inspiration from functional programming techniques, and represents mutations using pure functions. Mutations are done by computing a new state using the previous state.

// A pure mutationconst mutation = (previousState, action) => {
if (function responds to action) return {
... previousState,
// mutate fields
};
return previousState; // mutation does not respond to action
};

Representing mutations in this manner is useful, as it avoids a very common class of problems in interaction heavy applications, such as web apps. Mainly that mutations can stem from anywhere, and are often very unpredictable.

Let us start by looking at createStore.js, which, as the name suggests, is where we can find the createStore function in Redux.

function createStore(reducer, preloadedState, enhancer) {
[...] A bunch of error checks
if (enhancer) {
[...] We will get back to this later
}
[...] more error checking let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false

The first thing you might notice is that Redux does not use JavaScript classes/prototypes. Rather Redux captures the state inside a closure, which is an alternative way to do object orientation in JavaScript.

It has two nice features over classes and prototypes; There is no this and all the trouble it presents, and it allows for encapsulated internal state in the closure. Let us take a look at the difference between these approaches:

class Store {
constructor(reducer, preloadedState, ...) {
this.currentReducer = reducer;
this.currentState = preloadedState;
}
dispatch(action) {
this.currentState = this.currentReducer(action);
}
}
function createStore(reducer, preloadedState, ...){
let currentReducer = reducer;
let currentState = preloadedState;
function dispatch() {
currentState = currentReducer(action);
};
return {
dispatch
};
}

The main difference between these two snippets is that grabbing a reference to dispatch from an instance of Store is different from grabbing a reference to dispatch from an instance of createStore.

var ref1 = createStore().dispatch;
var ref2 = new Store().dispatch;

In the first case, calling dispatch could potentially do anything, since this could potentially be bound to anything.

this.currentReducer = function() { console.log('hello world'); }
ref2(); // console.log('hello world'); will be invoked

In the second case, currentReducer will be called.

Let us get back to createStore.js:

  function ensureCanMutateNextListeners() { [...] }  function getState() {
return currentState
}

Notice the state is fully accessible through the getter getState. But there is no way to set it. Store state should only be updated from inside a dispatch call:

  function subscribe(listener) { [...] }  function dispatch(action) {
[...] error checking
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
[...] updater listeners return action
}

The dispatch function calls the pure reduce function inside a catch block and sets the currentState to whatever the result of the reducer is. This is the only place in the source code where currentState is set. Redux does not allow for reducers to dispatch new actions. There is a very good reason for this; it can lead to very unpredictable code and in the worst case, an endless loop.

  function replaceReducer(nextReducer) {
[...]
}
function observable() { [...] } dispatch({ type: ActionTypes.INIT }) return {
dispatch,
getState,
replaceReducer,
observable,
subscribe
}
}

Redux initialises the store with an INIT action. This is meant to be used for both third party and your own code to potentially reset state.

Lastly Redux implements the observable interface and a subscribe function. The purist in me would argue that this is unnecessary, and one implementation would suffice, but it seems the authors of Redux decided to support both ways.

Now, coding your application using a single huge reducer is not exactly ergonomic. Rather reducers should be composed together. This is done by chaining/combining reducers together, and Redux ships with a smart way of doing so.

Redux assumes that each reducer handles some related subset of the application, commonly referred to as a module. The modules are combined into a single reducer, that produces an object where each module handles one property on the object. This means that reducers in individual modules do not affect the state of each other.

The combineReducers function is defined as follows:

export default function combineReducers(reducers) {
[...] error handling checking the type reducers keys and values
return function combination(state = {}, action) {
[...] more error handling of the action
let hasChanged = false
const nextState = {}
for each key and reducer in reducers {
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
[...] state must be a value
}

With the error checking and JavaScript idioms removed, the code is simple to follow. The function simply iterates over each module in the reducers object.

The last part of combineReducers.js is really important for semantic reasons and makes it clear why Redux assumes the functions are pure.

      nextState[key] = nextStateForKey
hasChanged ||= nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}

If you don’t return a new object from your reducer, but rather mutate the previous state, Redux will not detect the change, as the newly computed state and the old state are both compared by reference.

This is how Redux works on the surface, but using pure reducers gets old quickly, especially when doing asynchronous programming. Luckily Redux ships with support for extensions, by using enhancer functions.

Let’s take a look at createStore.js again. I skipped over a small section of the code:

function createStore(reducer, preloadedState, enhancer) {
[...] A bunch of error checks
if (enhancer) {
return enhancer(createStore)(reducers, preloadedState, enhancer)
}

What this does is it allows us to wrap the initialisation of the store, to add additional functionality to the Redux store.

Redux handle pure data actions pretty well, but in some cases, we want to perform more specialized actions. One such use case could be support for thunks. Thunks is another concept taken from functional programming and is essentially procedures. In this case, we could emit thunks as actions, and make them emit even more actions. This could be very useful for asynchronous sequences where we emit multiple actions depending on where in a computation we are.

The next section is a bit hairy, but it is designed in a way such that the store remains predictable, even in the presence of third party middleware.

Before diving into the implementation, let us take another detour. In OOP subclassing allows us to reuse parts of a previously defined class while extending or overwriting parts of the functionality. As we saw earlier, Redux shies away from using JavaScript OOP. But OOP-like subclassing can be implemented anyway, as is evident below:

function Constructor(params) { [...] construct some object }function Derived(OldConstructor) { 
return function(params){
const inst = OldConstructor(params);
return {
... inst,
extendSomeFunction: (params) => {
[...] do something before
inst.extendSomeFunction(params);
[...] do something after
},
overwriteSomeFunction: (params) => {
[...] assuming that inst has this function, change the behavior
}
}
}

This is how middlewares are implemented, by creating an enhancer, that allows for overwriting and replacing parts of the implementation, very much like subclassing in traditional OOP works. With this in mind, let’s examine applyMiddleware.js

export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {

The first part is is an implementation detail, it allows the construction of a applyMiddleware function with some predefined middleware already bound.

The next part takes the constructor function for a store, this is essentially the same pattern described in the previous section.

  const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))

First, this wrapped constructor creates an instance of the store. Then we partially apply the middleware functions using a subset of the store API.

Redux middleware has the following function signature:

const middleware = ({getState, dispatch}) => next => action => ...

This means that chain is a list of partially applied middleware functions, where the getState function from the original store is bound, and a dispatch which is bound to a closure bound dispatch function.

dispatch = compose(...chain)(store.dispatch)return {
...store,
dispatch
}
}
}

Then the partially applied chain is composed together into a single function. Below I try to illustrate what each step does:

const chain = [c0, c1, c2, ..., cn];
const sub = compose(...chain)
// sub = dispatch => action => c0(c1(c2(...cn(dispatch))))(action)
const dispatch = compose(...chain)(store.dispatch)
// dispatch = action => c0(c1(c2(...cn(store.dispatch))))(action)

The reason for this complicated setup is actually quite interesting. Middlewares can’t actually affect the original store unless they call the original dispatch function, which will be the very last middleware’s next function in chain. The middlewares can however, perform action both before and after calling the next function. Additionally, middlewares can dispatch new actions. This design only allows them a limited amount of options for mutating the store, which is a very useful property for a state manager that prides itself on predictable mutations. But at the same time, the design allows enough freedom for very powerful middleware.

Below an implementation of a thunk middleware is shown:

const thunk = API =>
next =>
action => typeof action === 'function' ? action(API)
: next(action);

This design works, as the reducers will only trigger as long as actions are passed into the next(action) part. This also satisfies the contract, that reducers cannot dispatch actions.

The last part of Redux is the bindActionCreators.js module/function. This is a utility function that can bind generic action creators together with the dispatch function.

The implementation of this function is actually quite simple. Given a dispatch function, and some map of action creators, bind each action creator in such a way that the action gets dispatched when the action creators get called.

function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
[...] commentsexport default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
[...] input validation handling const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i] // bind creator to this key
const actionCreator = actionCreators[key] // action creator
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(
actionCreator, dispatch
) // bind the function
} else { [...] }
}
return boundActionCreators
}

This part is pretty self explanatory. The code is mostly used in integrations between other frameworks, such as in React-Redux, where it binds the second parameter in the connect function to the store dispatcher.

The way Redux is designed, as decoupled functions, is very useful for testing. As there is no coupling between reducers, action creators, and the store. For an example by logging all actions, it is possible to replay a sequence that lead to an error.

Stripping Redux of input validation and comments you are left with about 100 lines of code. With this understanding, I suspect you can write your very own implementation of Redux in about 100 lines of code. I’ll leave this as an exercise to the reader.

I hope this helped demystify what Redux is. I wrote this article in an attempt to understand Redux myself, and I hope it helped you as well.

--

--