Redux Outside of React

Josh Miller

Declarative user interfaces are a relatively new idea in the world of web design. With the rise of React and the VirtualDOM, the world of web development is shifting to reactive views based on single page applications. This has given rise to new problems, namely, how do we manage state in an ever changing environment. Enter Redux: a minimal JavaScript framework for managing state in these types of applications.

Redux is extremely helpful when it comes to managing the flow of data in your application. The ideas behind Redux come from Elm and The Elm Architecture, a language that prioritizes this pattern within an application. While often associated with React, it’s minimal enough that it can easily be used in any JavaScript application. So, what’s another application that needs to manage state? Games!

"Great patterns aren’t created, they are discovered." Some Programmer

Let’s step back for a moment to take a look at what Redux is and the benefits of using it. At its core, it is merely a state container. It manages all the data flow throughout your app. It does this following three principles that are the key to Redux.

  1. Single source of truth
    The entire state of your app is a single object tree.

  2. Read-only state
    The only way to change state is through an action function.

  3. Changes to state are done with pure functions
    State changes are made by functions with no side effects.

By following these rules we get guarantees and code that is easy to understand. By changing state with pure functions we ensure that our state tree keeps the same shape as our data changes. By having a read-only state we insure that our data is unidirectional and can only be changed through defined actions. By having a single source of state, we don’t have random data strewn throughout our application. Following these rules and patterns gives us assurances that allow us to scale code more quickly and safely.


Model the Problem

So where do we start? We start by modeling our application’s state. In Redux this is called a Reducer, and it is what defines the shape of our state. For this article, we’ll create a simple game where you click on a airplane and it moves to a random location in the game screen. So, our Reducer needs to store the coordinates of our airplane as we play the game.

// Reducers
function coordinates(state = [1, 1], action) {
   switch(action.type) {
    case CLICK:
        return [
        state[0] + 40 * Math.random(),
        state[1] + 40 * Math.random()
      ]
    default:
        return state;
  }
}

As you can see, our Reducer is nothing more than a stateless function. We pass in our initial state value, which is an array of two numbers that represent x and y points. We also pass in our action object. The action object is used in a switch statement where we check for the action type. Then, based on the type, we update our state. An important thing to note here is that we are not directly modifying state, we are returning an entirely new array that is based on the previous state values. Now we have our Reducer function which returns a single array.

Our example is fairly simple and contains a single state type. This is a bit unrealistic for applications where there is more data. Luckily Reducers are just functions. They can be easily combined with one another to form a larger state tree. In fact, Redux has a handy helper function to help you with this called combineReducers.


Single Source of Truth

Once we have our Reducer we need to create our Store. If the Reducer is the shape of our data then the Store is the canonical source of our data. It is where our current state lives, and it is created with this code.

// Store
let store = createStore(coordinates);

As you can see, our Store is created using our reducer function. So when this code is run, it fires our Reducer which will return our initial state values. In Redux, we can check state at anytime by using the store.getState() method.


Action!

Since our state tree is to be considered immutable we need to define a way to update it through our Reducer. We do this through creating action types and objects.

// Actions
const CLICK = 'CLICK';
function click() {
    return {
    type: CLICK
  };
}

First, we define a type as a string constant. This is used in our Reducer’s switch statement to determine which action we are performing. Then we create a function that returns our action object. Our action object only requires a type property but can also contain other properties if we need to pass additional data to our Reducer.


Wiring it Up

We now have everything defined and mapped for our state. Now we need to hook this into our game inputs, which is done through the store.dispatch() method. This method fires our Redux actions and is the only way to actually update our state. So, we attach a click event to the airplane sprite that dispatches our action.

// When you click the plane, fire our click action
plane.events.onInputDown.add(() => {store.dispatch(click())}, null);

We now have our state being updated as we click on the plane in our game. But we need our plane to move to the x and y coordinates that our updated state contains. To do this we use store.subscribe(). This method lets us subscribe a listener function to state changes in our game. Here we add a tween function that will transition our plane to the current state's x and y coordinates.

// Subscribers
function movePlane(plane) {
    game.add.tween(plane).to({x: store.getState()[0], y: store.getState()[1] }, 1000, 'Linear', true);
}
 
// Subscribe function to store changes
store.subscribe(movePlane.bind(null, plane));

Let’s take a step back and look at the flow of our game:

Dispatch click action → Update coordinates reducer → Subscribers react to new state

A simple flow of events is now being run through Redux. It’s very easy to understand how state is changing in our game. We don’t have to worry about other functions updating the coordinates of our plane because we are using the Store as our single source of truth. So long as we continue to follow this pattern we don’t have to worry about outside effects mutating our game.


Redux gives us the guarantees that allow us to write better programs. By following the rules we can easily follow the flow of our applications, which gives us benefits like easing the refactor process. Redux is also very minimal. Most of our code is simply pure functions, which lets us write solid tests. All this said, Redux gives a lot of benefits to your coding. It can be used in any application where you need to manage state. The benefit of Redux is it isn’t a magical framework; it follows a simple pattern that gives you assurances with minimal code cost.

Here is our finished game!