It’s no secret that Redux took the front-end world by storm. Flux had people in doubts but it offered hope and when Redux reared its head, we realized that things can be simple and front-end state management can be simpler than “I hope it works!”.

But for many, Redux is quite an enigma. From its heavy usage of ES2015 concepts to a data flow which doesn’t immediately make sense, it baffles users left and right. Honestly, I’ve yet to see a definitive resource on how Redux works and advanced usage.

I won’t deal with advanced usage; however, I want to offer a peak under the hood. I want to show you what Redux is without using Redux. We’ll build Redux from scratch based on the concepts and design patterns the library uses but without covering all the edge-cases.

And in fact, we’ll build it (mostly) based on API knowledge without peaking at the source code!

Check it out on Github

The Goals – Building Alterdux

Before we go further, let’s define the goals of this new Redux alternative dubbed Alterdux:

  1. Create Store
  2. Create Dispatch/Reduce/Subscribe lifecycle
  3. Create an unsubscribe method for clean up
  4. Create enhancer support
  5. Make it combatible with applyMiddleware, DevTools and react-redux

That’s pretty standard so on top of that, a little cherry on top, we’ll add a couple new features at the end to show how this pattern can be bent:

  1. On-the-fly addition of new reducers (similar to what Paradux does)
  2. Built-in logger that can be toggled with a config

Those seem like lofty goals but you have to realize that Redux with all of its safeties and optimizations is only about 100 lines of code.

The Store

Creating a store in Redux is really simple. We’ll keep things just as functional but the goal is to be able to do this:

var store = createStore(reducer, initialState, enhancers);

The last argument may look a tad strange. Enhancers, what are those? If you already know Redux, you might think this is an array of middlewares, but alas, it is not. The applyMiddleware function which Redux provides actually creates an enhancer from the Middlewares you pass in. We’ll skip that part for last.

Let’s tackle the createStorefunction. We already know that reducers is really just one big function that gets run every time the state updates. Let’s go ahead and spec out this function real quick:

function createStore(reducer, initialState) {
  var state = initialState;

  return {};
}

Great, we blindly saved the reducer and the initialState as the actual state. Note that neither of those properties are publicly accessible. We’ll go ahead and scope out the entire store to be within that createStore function (or rather, it’s the this).

The store is supposed to expose a couple of functions, one of them is dispatch and the other one is subscribe.

The Store And The Observer Pattern

A store holds the state of our application but without going into too much detail, it’s actually a combination of two architectural patterns or “design” patterns. One of them is Observer (read more here: http://gameprogrammingpatterns.com/observer.html).

The Observer pattern is basically the idea that you’re watching a subject for changes and when a change happens, the subject tells its watchers that it changed. Common implementations of this pattern are: publish/subscribe (or pubsub), evented systems (like the DOM event listeners), and…well, Redux.

In Redux, the store holds a javascript object called state. When the state updates, it notifies all of its subscribers. A big part of this pattern that you can subscribe and unsubscribe on the go.

Let’s create this function so that it makes more sense

Creating Subscribe function

Let’s go ahead and add the subscribe function first:

function createStore(reducer, initialState) {
  // ... saving our vars
  var subscribers = []; // our subscriber array

  function subscribe(listener) {
    subscribers.push(listener);

    var unsubscribed = false;

    // returns unsubscribe function
    return function() {
      if (!unsubscribed) {
        subscribers.splice(subscribers.indexOf(listener), 1);
        unsubscribed = true;
        return true;
      }

      return false;
    };
  }

  return { subscribe };
}

We keep an array of subscribers that we will later notify when the state changes. In the subscribe function, we’re adding onto that array and returning an unsubscribe function so that when a component unmounts, it can make sure that it no longer receives state updates. We also make sure that once we’ve unsubscribed, we can’t “unsubscribe” again.

Creating The Dispatch Function

Since I’m not a 100% sure which pattern dispatch really fits into, I’m going to just say that it’s part of a generic pub/sub system that Redux (kind of) resembles. You publish an action to the store, the store uses its reducers to modify the state based on that action, and subscribers get their data.

What we want dispatch to really do is the following:

  1. pass current state and action to reducer
  2. save new state

Let’s see what that code would look like (and I’ll go ahead and just show the function):

// ... create store function starts here
function dispatch(action) {
  state = reducer(state, action);
  subscribers.forEach((subscriber) => {
    subscriber(newState);
  });
};
// ... create store ends here.
return { subscribe, dispatch };

And that’s it! Congratulations, we’ve pretty much just created the basis on which Redux stands.

Note: Redux doesn’t actually pass state through the subscriber. It leaves that part to the user to require and run store.getState(); however, I wanted to utilize this little change to bring about a better experience for the tutorial. Tools like react-redux and create this same shortcut via connect().

The Alterdux so far

Go ahead and check it out!

See the Pen alterdux store by Antonin Januska (@AntJanus) on CodePen.

The short version is this:

  1. we can create a store
  2. we can subscribe to state changes
  3. we can unsubscribe
  4. and we can dispatch changes

Congratulations! This is Redux in a nutshell!

Enhancer

I actually had to check the source for this one. So I thought that Redux supported middleware out of the box but it actually doesn’t! It supports enhancers. An enhancer is basically a function that takes the store itself as an argument, it can modify it, wrap it, do whatever it wants to and it returns the result.

Basically, an enhancer is the equivalent of doing this:

var store = enhancer(createStore, reducer, initialState);

But in a nicer format:

var store = createStore(reducer, initialState, enhancer);

The weird thing is that there is no documentation on enhancers in the Redux docs.

To diffuse any confusion, Redux does ship with the applyMiddleware enhancer which adds middleware support.

A Basic Enhancer

Before we get into middleware, let’s create a basic enhancer. We’re getting a little into the weeds here since using multiple enhancers requires function composition (wrapping functions within functions within functions etc.). Let’s back up.

Here’s an enhancer that doesn’t actually do anything:

function myEnhancer(createStore) {
  return createStore;
}

Not really doing anything there but that’s pretty much what enhancers look like. You can also overwrite or add new functionality. Let’s go ahead and add a new method that fires off an action every time a new subscriber is added:

function subscribeNotify(createStore) {
  return (reducer, initialState, enhancer) => {
    const store = createStore(reducer, initialState, enhancer)
    let subscribe = store.subscribe;
    let dispatch = store.dispatch;

    var enhancedSubscribe = (listener) => {
      dispatch({ type: 'SUBSCRIBE', payload: listener });

      return subscribe(listener);
    };

    return Object.assign({}, store, { subscribe: enhancedSubscribe })
  }
}

I make no promises but the code above should simply dispatch a SUBSCRIBE action every time a new listener subscribes to the store.

Building Enhancer Support

Go back to the createStore function and all the way on top, let’s add the support:

function createStore(reducer, initialState, enhancer) { // note the new argument
  if (enhancer) {
    return enhancer(createStore)(reducer, initialState);
  }

  // ... rest of the function
}

This little if statement short-circuits the creation of the entire store and instead, passes the createStore function itself to the enhancer. Then it passes the reducer and initialState to the result of that enhancement.

This is the equivalent of doing this:

var store = enhancer(createStore)(reducer, initialState);

But looks much prettier for the developer.

So far

We should have no issue using the DevTools provided for Redux and applyMiddleware with Alterdux. In fact, there should be no noticeable difference in the implementation.

Before we move on, here’s a codepen demonstrating the subscriberNotify enhancer:

See the Pen alterdux notifySubscriber by Antonin Januska (@AntJanus) on CodePen.

Paradux-like Enhancer

A few weeks ago, I built a library called Paradux. What Paradux essentially did was give you the ability to add and remove reducers at run time, getting rid of the “set it up once” store idea.

I don’t want to get into the nitty gritty but what I want to essentially do here is:

  1. add a new method called addReducer
  2. wrap the initial reducer in our enhanced reducer which can run the runtime reducers

Let’s see if we can create that real quick:

function paraduxEnhancer(createStore) {
  return (reducer, initialState, enhancer) => {
   let reducers = [];

   function addReducer(reducerFunc) {
     reducers.push(reducerFunc);
     var unsubscribed = false;

     return function() {
       if (!unsubscribed) {
         reducers.splice(reducers.indexOf(reducerFunc), 1);
         unsubscribed = true;

         return true;
       }

       return false;
     }
   }

   function enhancedReducer(reducer) {
      return (state, action) => {
        var newState = reducers.reduce((tempState, reducerFunc) => {
          return reducerFunc(tempState, action);
        }, reducer(state, action));

        return newState;
      };
   }

   const store = createStore(enhancedReducer(reducer), initialState, enhancer);

   return Object.assign({}, store, { addReducer });
  }
}

Cool! So with this enhancer, we can do the following:

const store = createStore(reducer, initialState, paraduxEnhancer);

// run time added reducer!
var unsubscribe = store.addReducer(someNewReducer);

What we basically did was wrap the store to add the addReducer functionality that keeps an array of runtime reducers, and we also wrapped the supplied reducer so that it runs first, and then we run our runtime reducers.

We’ve also copied the subscribe function’s ability to self-unsubscribe when we add reducers.

Cool! With this, we can easily do the following:

var store = createStore(reducer, initialState, paraduxEnhancer);
var unsubscribeRuntimeReducer = store.addReducer(function(state, action) { return state; }); // to add a new reducer into the mix
unsubscribeRuntimeReducer(); // to remove it

Here’s a codepen of what that looks like:

See the Pen alterdux paraduxEnhancer by Antonin Januska (@AntJanus) on CodePen.

Toggleable Logger Enhancer

A logger is usually applied as a middleware but what if we could use it as an enhancer that we can toggle?

Here’s what that would look like:

function toggleableLogger(createStore) {
  return (reducer, initialState, enhancer) => {
    const store = createStore(reducer, initialState, enhancer);
    var toggle = false;

    let toggleLog = () => { !toggle };

    let dispatch = (state, action) => {
      if (toggle) { console.log(state, action) }
      return store.dispatch(state, action);
    }
    return Object.assign({}, store, { dispatch, toggleLog });
  };
}

By simply running store.toggleLog() we get debug logging. Running it again, and the logging turns off.

And that’s it!

We’ve built an alternative to Redux without peeking much under the hood. And honestly, after looking under the hood, there are only small differences in implementation. A lot of these have to do with safeties than much of anything else.