Let’s Write Redux!

David Drew
Jun 9, 2016 · 15 min read

Redux is pretty simple. You have action creators, actions, reducers and a store. What’s not so simple is figuring out how to put all of that together the best or most “correct” way. At least this is the problem I had with it. In order to try to gain a better understanding of the philosophy behind Redux, and to gain knowledge I could further share with my team, I decided to rewrite Redux, and to document it.

To rewrite Redux, I used this wonderful article by Lin Clark as a reference point, as well as the Redux codebase itself, and of course, the Redux docs.

Note: I’m using traditional pre-ES6 Javascript throughout this article. It’s because everyone who knows Javascript knows pre-ES6 JS, and I want to make sure I don’t lose anyone because of syntax unfamiliarity.

The Store

Redux, as is the same with any data layer, starts with a place to store information. Redux, by definition of the first principle of Redux, is a singular shared data store, described by its documentation as a “Single source of truth”, so I think I’ll start by making the store a singleton:

var globalStore;function getInstance() {
if (!globalStore) globalStore = createStore();
return globalStore;
}
function createStore() {
return {};
}
module.exports = getInstance();

The Dispatcher

The next principle is that the state of the store can only change in one way: through the dispatching of actions. What we mean by dispatching, is that we want to create a single interface to the wider app that updates state. We can dispatch an action from anywhere, and we know it will be handled through the proper channels. So let’s go ahead and write a dispatcher.

However, in order to update state in this dispatcher, we’re going to have to have state to begin with, so let’s update our createStore function, and rather than just return an empty object, we’ll create a simple object that contains our current state.

function createStore() {
var currentState = {};
}

We’ll eventually return an instance with methods from createStore, but we have to figure out what we need first, so for now, we’ll just have createStore return nothing.

Also, to dispatch an action, we need a reducer to dispatch it to; that is to say we need a function (a reducer is just a pure function) that accepts an action (which is just an object that looks kind of like a struct) and the current state (another object that holds values) as parameters. Let’s create a default one for now. A reducer receives the current state and an action and then returns a new version of the state based on what the action dictates:

function createStore() {
var currentState = {};

var currentReducer = function(state, action) {
return state;
}
}

This is just a default function to keep the store from crashing until we formally assign reducers, so we’re going to go ahead and just return the state as is. Essentially a “noop”.

The store is going to need a way to notify interested parties that the state has changed, so let’s create an array to house subscribers:

function createStore() {
var currentState = {};

var currentReducer = function(state, action) {
return state;
}

var subscribers = [];
}

Cool! OK, now we can finally put that dispatcher together. As we said above, actions are handed to reducers along with state, and we get a new state back from the reducer. If we want to retain the original state before the change for comparison purposes, it probably makes sense to temporarily store it.

(Note that the real Redux library doesn’t pass the state prior to change to the subscribers — I think this might be because Redux was built with React in mind, and React already has mechanisms to track deltas in the state.)

Since an action is dispatched, we can safely assume the parameter a dispatcher receives is an action.

function createStore() {
var currentState = {};
var currentReducer = function(state, action) {
return state;
}

var subscribers = [];
function dispatch(action) {
var prevState = currentState;
}

return {
dispatch: dispatch
};
}

We also have to expose the dispatch function so it can actually be used when the store is imported. Kind of important. We do so by returning an object that has a property assigned a reference to the dispatch function.

So, we’ve created a reference to the old state. We now have a choice: we could either leave it to reducers to copy the state and return it, or we can do it for them. Since receiving a changed copy of the current state is part of the philosophical basis of Redux, I’m going to go ahead and just hand the reducers a copy to begin with.

(Important update: This is something else that the real Redux library does not do, and after I implemented this, I found it makes sense: if you want to quickly determine if the state’s changed, you can do a === compare on two object references to see if they match. You lose the ability to do this if the state is always copied, since it creates a new object. It’s more optimal to not copy if you don’t have to, not to mention faster, anyway. Deep cloning is expensive.)

function createStore() { 
var currentState = {};
var currentReducer = function(state, action) {
return state;
}
var subscribers = []; function dispatch(action) {
var prevState = currentState;
currentState = currentReducer(cloneDeep(currentState), action);
}
return {
dispatch: dispatch
};
}

We hand a copy of the current state and the action to the currentReducer, which uses the action to figure out what to do with the state. What is returned is a changed version of the copied state, which we then use to update the state. Also, I’m using a generic cloneDeep implementation (in my actual implementation, I used lodash’s cloneDeep) to handle copying the state completely. Simply using Object.assign wouldn’t be suitable because it retains references to objects contained by the base level object properties.

Now that we have this updated state, we need to alert any part of the app that cares. That’s where the subscribers come in. We simply call to each subscribing function and hand them the current state and also the previous state, in case whoever’s subscribed wants to do delta comparisons:

function createStore() { 
var currentState = {};
var currentReducer = function(state, action) {
return state;
}
var subscribers = []; function dispatch(action) {
var prevState = currentState;
currentState = currentReducer(cloneDeep(currentState), action);

subscribers.forEach(function(subscriber){
subscriber(currentState, prevState);
});
}
return {
dispatch: dispatch
};
}

Of course, none of this really does any good with just that default noop reducer. What we need is the ability to add reducers, as well.

Adding Reducers

In order to develop an appropriate reducer-adding API, let’s revisit what a reducer is, and how we might expect reducers to be used.

In the Three Principles section of Redux’s documentation, we can find this philosophy:

So what we want to accommodate is something that looks like a state tree, but where the properties of the state are assigned functions that purely change their state based on the current state and an action.

{ 
stateProperty1: function(state, action) {
// does something with state and then returns it
},
stateProperty2: function(state, action) {
// same
}, …
}

Yeah, that looks about right. We want to take this state tree object and run each of its reducer functions every time an action is dispatched.

We have currentReducer defined in the scope, so let’s just create a new function and assign it to that variable. This function will take the pure reducers we passed to it in the state tree object, and run each one, returning the outcome of the function to the key it was assigned.

function createStore() { 
var currentReducer = function(state, action) {
return state;
}
function addReducers(reducers) {
currentReducer = function(state, action) {
var cumulativeState = {};

for (key in reducers) {
cumulativeState[key] = reducers[key](state[key], action);
}

return cumulativeState;
}
}
}

(Update: The real Redux library has no such method as “addReducers”. You can either assign a singular reducer when you create the store, or replace that singular reducer wholesale. This is likely just to keep the API simple. You’ll find that libs like react-redux sort of have to work around this limitation in weird ways. I address it further down in the article with my own React interface.)

Something to note here: we’re only ever handing a subsection of the state to each reducer, keyed from its associated property name. This helps simplify the reducer API and also keeps us from accidentally changing other state areas of the global state. Your reducers should only be concerned with their own particular state, but that doesn’t preclude your reducers from taking advantage of other properties in the store.

As an example, think of a list of data, let’s say with a name “todoItems”. Now consider ways you might sort that data: by completed tasks, by date created, etc. You can store the way you sort that data into separate reducers (byCompleted and byCreated, for example) that contain ordered lists of IDs from the todoItems data, and associate them when you go to show them in the UI. Using this model, you can even reuse the byCreated property for other types of data aside from todoItems! This is definitely a pattern recommended in the Redux docs.

Now, this is fine if we add just one single set of reducers to the store, but in an app of any substantive size, that simply won’t be the case. So we should be able to accommodate different portions of the app adding their own reducers. And we should also try to be performant about it; that is, we shouldn’t run the same reducers twice.

// State tree 1 
{
visible: function(state, action) {
// Manage visibility state
} …
}
// State tree 2
{
visible: function(state, action) {
// Manage visibility state (should be the same function as above)
} …
}

In the above example, you might imagine two separate UI components having, say, a visibility reducer that manages whether something can be seen or not. Why run that same exact reducer twice? The answer is “that would be silly”. We should make sure that we collapse by key name for performance reasons, since all reducers are run each time an action is dispatched.

So keeping in mind these two important factors — ability to ad-hoc add reducers and not adding repetitive reducers — we arrive to the conclusion that we should add another scoped variable that houses all reducers added to date.

function createStore() { 

var currentReducerSet = {};
function addReducers(reducers) {
currentReducerSet = Object.assign(currentReducerSet, reducers);
currentReducer = function(state, action) {
var cumulativeState = {};
for (key in currentReducerSet) {
cumulativeState[key] = currentReducerSet[key](state[key], action);
}

return cumulativeState;
}
}

The var currentReducerSet is combined with whatever reducers are passed, and duplicate keys are collapsed. We needn’t worry about “losing” a reducer because two reducers will both be the same if they have the same key name. Why is this?

To reiterate, a state tree is a set of key-associated pure reducer functions. A state tree property and a reducer have a 1:1 relationship. There should never be two different reducer functions associated with the same key.

This should hopefully illuminate for you exactly what is expected of reducers: to be a sort of behavioral definition of a specific property. If I have a “loading” property, what I’m saying with my reducer is that “this loading property should respond to this set specific actions in these particular ways”. I can either directly specify whether something is loading — think action name “START_LOADING — or I can use it to increment the number of things that are loading by having it respond to action names of actions that I know are asynchronous, such as for instance LOAD_REMOTE_ITEMS_BEGIN” and “LOAD_REMOTE_ITEMS_END”.

Let’s fulfill a few more requirements of this API. We need to be able to add and remove subscribers. Easy:

function createStore() { 
var subscribers = [];
function subscribe(fn) {
subscribers.push(fn);
}
function unsubscribe(fn) {
subscribers.splice(subscribers.indexOf(fn), 1);
}
return {

subscribe: subscribe,
unsubscribe: unsubscribe
};
}

And we need to be able to provide the state when someone asks for it. And we should provide it in a safe way, so we’re going to only provide a copy of it. As above, we’re using a cloneDeep function to handle this so someone can’t accidentally mutate the original state, because in Javascript, as we know, if someone changes the value of a reference in the state object, it will change the store state.

function createStore() { 
function getState() {
return cloneDeep(currentState);
}
return {

getState: getState
};
}

And that’s it for creating Redux! At this point, you should have everything you need to be able to have your app handle actions and mutate state in a stable way, the core fundamental ideas behind Redux.

Let’s take a look at the whole thing (with the lodash library):

var _ = require(‘lodash’); 
var globalStore;
function getInstance(){
if (!globalStore) globalStore = createStore();
return globalStore;
}
function createStore() {
var currentState = {};
var subscribers = [];
var currentReducerSet = {};

currentReducer = function(state, action) {
return state;
};

function dispatch(action) {
var prevState = currentState;
currentState = currentReducer(_.cloneDeep(currentState), action);
subscribers.forEach(function(subscriber){
subscriber(currentState, prevState);
});
}

function addReducers(reducers) {
currentReducerSet = _.assign(currentReducerSet, reducers);
currentReducer = function(state, action) {
var ret = {};

_.each(currentReducerSet, function(reducer, key) {
ret[key] = reducer(state[key], action);
});

return ret;
};
}

function subscribe(fn) {
subscribers.push(fn);
}

function unsubscribe(fn) {
subscribers.splice(subscribers.indexOf(fn), 1);
}

function getState() {
return _.cloneDeep(currentState);
}

return {
addReducers,
dispatch,
subscribe,
unsubscribe,
getState
};
}
module.exports = getInstance();

So what did we learn by rewriting Redux?

We learned a few valuable things in this experience:

  1. We must protect and stabilize the state of the store. The only way a user should be able to mutate state is through actions.
  2. Reducers are pure functions in a state tree. Your app’s state properties are each represented by a function that provides updates to their state. Each reducer is unique to each state property and vice versa.
  3. The store is singular and contains the entire state of the app. When we use it this way, we can track each and every change to the state of the app.
  4. Reducers can be thought of as behavioral definitions of state tree properties.

Caveats

The Redux we wrote above has one notable differences from the actual Redux library: there is no “addReducers” method on the real Redux library.

With Real Redux, you can either assign a reducer initially, when creating the store or replace the reducer wholesale later on. This is great if your entire app is Redux, but if you’re like us, and only working on it in parts of your app to try it out (or migrating to it), you need the ability to ad-hoc change the reducers. The way I worked around this was by writing a shim that had an “addReducers” method on it, and wrapped Redux. When you add a state tree of reducers, it will merge it with the state tree it currently has and call “replaceReducers” on Real Redux.

Here’s that wrapper:

import { createStore, combineReducers } from 'redux';

var reduxStore = createStore(function(state) { return state; }, {});

function Store() {

var cumulativeReducers = {};

function addReducers(reducerObj) {
cumulativeReducers = _.assign(cumulativeReducers, reducerObj);
reduxStore.replaceReducer(combineReducers(cumulativeReducers));
}

return {
addReducers,
dispatch: reduxStore.dispatch,
subscribe: reduxStore.subscribe,
getState: reduxStore.getState
}
}

export default new Store();

Bonus section: a React adapter

Having the store is nice, but you’re probably going to want to use it with a framework. React is an obvious choice, as Redux was created to implement Flux, a core principle data architecture of React. So let’s do that too!

You know what would be cool? Making it a higher-order component, or HOC as you’ll sometimes see them called. We pass an HOC a component, and it creates a new component out of it. And it is also able to be infinitely nested, that is, HOCs should be able to be nested within each other and still function appropriately. So let’s start with that basis:

Note: Going to switch to ES6 now, because it provides us with class extension, which we’ll need to be able to extend React.Component.

import React from ‘react’;
export default function StoreContainer(Component, reducers) {
return class extends React.Component { }
}

When we use StoreContainer, we pass in the Component class — either created with React.createClass or React.Component — as the first parameter, and then a reducer state tree like the one we created up above:

// Example of StoreContainer usage 
import StoreContainer from ‘StoreContainer’;
import { myReducer1, myReducer2 } from ‘MyReducers’;
StoreContainer(MyComponent, {
myReducer1,
myReducer2
});

Cool. So now we have a class being created and receiving the original component class and an object containing property-mapped reducers.

So, in order to actually make this component work, we’re going to have to do a few bookkeeping tasks:

  1. Get the initial store state
  2. Bind a subscriber to the component’s setState method
  3. Add the reducers to the store

We can bootstrap these tasks in the constructor lifecycle method of the Component. So let’s start with getting the initial state.


export default function StoreContainer(Component, reducers) {
return class extends React.Component {

constructor() {
super(props);
// We have to call this to create the initial React
// component and get a `this` value to work with
this.state = store.getState();
}
}
}

Next, we want to subscribe the component’s setState method to the store. This makes the most sense because setting state on the component will then set off the top-down changes the component will broadcast, as we’d want in the Flux model.

We can’t, however, simply send this.setState to the subscribe method of the store — their parameters don’t line up. The store wants to send new and old state, and the setState method only accepts a function as the second parameter.

So to solve this, we’ll just create a marshalling function to handle it:


import store from ‘./Store’;
function subscriber(currentState, previousState) {
this.setState(currentState);
}
export default function StoreContainer(Component, reducers) {
return class extends React.Component {
componentWillMount() {
this.unsubscribe = store.subscribe(subscriber.bind(this));
}
componentWillUnmount() {
this.unsubscribe();
}
}
}

Since the store is a singleton, we can just import that in and call on its API directly. In a moment, we’ll explain why we are subscribing in the componentWillMount phase of the the cycle.

Another thing to note: we follow Real Redux’ subscribe model here, as opposed to the one implemented above.

One last thing to do: add the reducers. This is as simple as passing what we received to the HOC into the store.addReducers method:


export default function StoreContainer(Component, reducers) {
return class extends React.Component {

componentWillMount() {

store.addReducers(reducers);
}

}
}

So why in componentWillMount? When we addReducers (which, remember, is actually replacing reducers), Real Redux kicks off an initialization update cycle. If that happens during a component’s construction phase, you will get the dreaded React invariant violation error, as you were trying to update a component out of phase (due to the fact you’ve subscribed this component to the store before you triggered the update).

So now we’re ready to provide the rendering of the component. This is the essence of HOCs. We take the Component we received and render it within the HOC, imbuing it with whatever properties the HOC needs to provide it:


export default function StoreContainer(Component, reducers) {
return class extends React.Component {

render() {
return (<Component {…this.props} {…this.state} />);
}
}
}

We are “spreading” the properties and state of the HOC down to the Component it is wrapping. This effectively ensures that whatever properties we pass to the HOC get down to the component it wraps, a vital feature of infinitely nestable HOCs. It may or may not be wise to place the state as properties on the Component, but it worked well in my testing, and it was nice being able to access to the state through the this.props object of the Component that is wrapped, as you might expect to normally do with a React component that receives data from a parent component.

Here’s the whole shabang:

import React from 'react';
var store = require('./StoreShim').default;

function subscriber() {
this.setState(store.getState());
}

export default function StoreContainer(Component, reducers) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = store.getState();
}

componentWillMount() {
store.addReducers(reducers);
this.unsubscribe = store.subscribe(subscriber.bind(this));
}

componentWillUnmount() {
this.unsubscribe();
}

render() {
return (<Component
{...this.props}
{...this.state} />);
}
}
}

Implementation of using StoreContainer:

import StoreContainer from ‘StoreContainer’; 
import { myReducer } from ‘MyReducers’;
let MyComponent extends React.Component {
// My component stuff
}
export default StoreContainer(MyComponent, { myReducer });

Implementation of using the Component that uses StoreContainer (exactly the same as normal):

import MyComponent from ‘MyComponent’; 
import ReactDOM from ‘react-dom’;
ReactDOM.render(<MyComponent myProp=’foo’ />, document.body);

But you don’t have to define the data basis of your MyComponent immediately or in a long-lasting class definition; you could also do it more ephemerally, in implementation, and perhaps this is wiser for more generalized components:

import StoreContainer from ‘StoreContainer’; 
import { myReducer } from ‘MyReducers’;
import GeneralizedComponent from ‘GeneralizedComponent’;
import ReactDOM from ‘react-dom’;
let StoreContainedGeneralizedComponent = StoreContainer(GeneralizedComponent, { myReducer });ReactDOM.render(<StoreContainedGeneralizedComponent myProp=’foo’ />, document.body);

This has the benefit of letting parent components control certain child component properties.

Conclusion

Well, that might have been a bit exhaustive, and we may not have covered everything but my hope is that by opening up Redux and exposing its innards, as well as providing an implementation of its usage with a popular library, it’s a bit more clear how it’s expected to manage state in a safe manner.

If you’ve found anything erroneous in this article or want to give feedback, please feel free to reach out to me to let me know so I can keep this article informative and up-to-date.

This article was originally featured on Jama Software’s Developer Insights blog on May 4, 2016.

David Drew

Written by

Frontend engineer, breaker and builder of things, generally.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade