What is Redux, and why should I care?

If you're using a single-page-app framework like Angular, React, Ember and so on, you'll have come up against exploding complexity as each new feature has to bolt itself onto all the previous bits and pieces, and work out how to share the interesting bits of it's state with other components in the app.

  • You have a login form, which has to pass the access token around to all the other parts of the app, without just dumping it on window.apikey.

  • You have a user profile form, and user-icons that need to update when a user uploads a new profile picture.

  • You've a massive controller, trying to manage all the components inside itself.

Each of these issues boil down to routing data between components.

Wouldn't it be better if these components just announced their interesting data, and anyone could pick and choose what they want? This is what Redux aims to solve:

  1. Pull the application state out of components. They should only be concerned with their own little world anyway, so why are they trying to manage other components' states for them?
  2. Make the application state globally available, and leave picking out the bits a component is interested in to the component itself.
  3. Only let components make changes with well defined Actions, to stop a rogue component doing something odd and breaking it for the rest of your application.

The outcome of this is thin controllers, simply piping data from the application state to components and triggering Actions as the user performs them; all state manipulation happening in one place; and streams of actions and easy-to-dump states for reproducing issues. (psst: this last one is cooler than it sounds; you can reset the state and replay the stream with the last few missing to rewind time. yeah.)

So, how does Redux pull this off?

  • Reducers
  • Action creators
  • Connections
  • Middleware

Reducers

If you've used .reduce on an array, you already know how these work.

If you haven't, reducers take two items and combine them into a single output. The simplest way to get your head around this is probably with an example. Here is one shamelessly lifted from the Redux repo:

function likeCounter(state = 0, action) {  
  switch (action.type) {
      case 'INCREMENT':
          return state + 1;
      case 'DECREMENT':
          return state - 1;
      default:
          return state;
  }
}

The likeCounter reducer is called with the current state, and an action. If the action's type is INCREMENT or DECREMENT it changes the state, otherwise the state stays the same. The state is then kept somewhere else (inside the redux store to be precise, not that the reducer has to care, thats the beauty of reducers) and when another action comes in, the new state is passed in, changed if necessary, then drops back out again. This is the essence of a reducer; its a function that decides how to react to an action, that is called over and over.

Action creators

So where does the action come from in the reducer above? Well, technically, any object with a .type parameter can be an action. Redux isn't fussy. But just making action objects at random means you can't change how an action works without hunting them all down. Enter, action creators.

function incrementLikeCounter() {  
    return {
        type: 'INCREMENT'
    };
}

This ones pretty simplistic, I know. But in future when you want to be able to add more than one at once, you could easily change to { type: 'INCREMENT_BY', amount: 1 } in just one function, and deprecate this simplistic INCREMENT action. Thats the beauty of having a function the generates the actions in stead of generating them yourself.

Connections

So we've got a state, and we've got actions. How do we get them plumbed in so we can use them in our components? Well, although Redux was initially created for React it is not dependant on it, it's interface is so simple it can be integrated into practically anything. For example:

// REACT
import ReactRedux from 'react-redux';  
import LikeActions from './actions/like';

const MyComponent = // react component goes here...

ReactRedux.connect(  
    state => ({ likes: state.likeCounter }),
    { increment: LikeActions.increment }
)(MyComponent);
// ANGULAR
angular.module('my.module', [ 'ngRedux' ])  
    .controller('MyController', function($scope, $ngRedux, LikeActions) {
        $ngRedux.connect(
            state => ({ likes: state.likeCounter }),
            { increment: LikeActions.increment }
        )($scope);

        // the rest of your controller goes here...
    })

in both of these cases, 'likes' would be the number of likes stored in the likeCounter, and calling the 'increment' function would increment that number (passing an Action Creator through .connect automatically dispatches it for you too).

And there you have it. You can store your state completely decoupled from the component. So decoupled that you can use it in two entirely different frameworks, and have both of them work from the same state object. And as soon as you press the 'like' button in one place, the counter goes up everywhere that it is referenced.

But how about something more complex? What if you're talking to a server, maybe you need to log in, and so on?

Middleware

As we have been pulling state out of controllers, we should also be pulling complexity out of reducers. Redux disallows reducers triggering more actions (primarily because its very easy to get into an infinite loop that way), but provides Middleware as an alternative.

Middleware functions are called with the store object, the next middleware in line, and then finally the action to process:

import LikeSeverService from './services/LikeServer';

const likeChecker = store => next => action => {  
    switch (action.type) {
        case 'INCREMENT':
            LikeServerService.hasLiked(store.getState().apikey)
                .then(hasLiked => !hasLiked ? next(action) : null);
            break;

        case 'DECREMENT':
            LikeServerService.hasLiked(store.getState().apikey)
                .then(hasLiked => hasLiked ? next(action) : null);
            break;

        default:
            next(action);
            break;
    }
}

(if you're new to ES2015 syntax and the above confuses you, the chain of fat arrows => means you call this as likeChecker(store)(next)(action) - although in practice Redux calls with (store)(next) then keeps that function, calling it on each Action that gets dispatched)

So, if an INCREMENT action is dispatched, we hold onto it until we're sure the user hasn't already liked it; vice versa for DECREMENT; and if its neither of these actions we just pass it along to the next in the chain with next(action).

This is pretty powerful. As you can dispatch actions inside a middleware, you can do things like dispatch a warning Action if something is already liked, without the 'like' button having to know anything about the LikeServerService, or where to find the apikey!

Conclusion

Redux gives you separation of concerns, in spades. If you want to know what can change the apikey, check the apikey reducer and look at the actions in the switch statement.

Want to load your own user icon as soon as you log in? Have your userProfile middleware listen for the LOGGED_IN action and automatically trigger to load your
profile info.

Want to know if you're logged in? Check if you have an apikey.

Want to rewind time and see what caused that bug? Log all your actions then play back without some until you find the action that caused it.

I hope this has whet your appetite for Redux, and that you can get your state-spaghetti organised.