How to Make State Management Suck Less With Redux

Maintaining state in an application can be tricky, but it doesn’t have to be. Managing how and when state changes can be accomplished with Redux, a JavaScript state manager. Today we are going to set up a very basic counter with Redux and no React so we can explore the fundamentals of Redux. After this gentle introduction, you should become more comfortable with using Redux in your future React and non-React projects. If you don’t want to read over this, you can explore the demo here

A Counter Using Redux

Setup

This guide assumes you are comfortable / dangerous with JavaScript.

Let’s start with our file structure. We are going to use a tiny bit of webpack to compile our application. You can start by cloning this repository to start following along. It does live reloading and comes webpack 3 ready.

After we install our dependencies with npm install, we will want to add Redux to our application. We can do this easily with npm install — save redux. Once all done, you can navigate to localhost:8080 and see a nice welcome message. I’m going to start by cleaning out everything in app/entry.js and app/app.js

The Counter

Let’s update the HTML inside of static/index.html to have two buttons an h1 tag. We’re going to keep it very simple and just do something like this:


<! — static/index.html →
<div class=”container”>
<h1 id=”counter”>0</h1>
<button class=”waves-light btn” id=”plus”> + </button>
<button class=”waves-light btn” id=”minus”> — </button>
</div>

Of course, no functionality. If you were to build this counter, how would you do it? Maybe you could add some event listeners to the buttons that change the innerHTML of the counter. This would be an easy approach, but we’re interested in Redux. To do this, we’re going to move the entire state of our counter application into a Redux Store.

Store

The Redux Store holds the entire state tree of your application. So we could say something like currentCount: 0 and have our buttons update that property of our state tree. Let’s go ahead and create a new file in our app directory and call it store.js


// app/store.js
const redux = require(‘redux’)
var store = redux.createStore()
module.exports = store

We know have a ‘container’ to hold different pieces of state. A reducer is used for managing different parts of our state. Let’s create a reducer so we can create our currentCount state.

Reducers

A Reducer describes how the application state changes over time. We can say that the currentCount of our app starts at 0, 1, or any number we would like. When the user presses the plus or minus button, we expect a reducer to handle this change.

Let’s create a new directory called reducers and set up a counter_reducer.js in there. We’re going to create a place to hold our currentCount and for now, we will set it to 0.


// reducers/counter_reducer.js
var initialState = {
currentCount: 0
}
var counterReducer = (state = initialState, action) => {
return state
}
module.exports = counterReducer

We first create an object called initialState which has a single property called currentCount in here, we have set the currentCount to 0.

We then create our counterReducer which is a function. All reducers are functions as they are meant to take in request to change some piece of state and update it. Each reducer takes in some state, in this case it’s initialState, followed by an action, the request to update that piece of state. We will explore actions a bit later, for now just know that argument is there and will be used in the near future. This function’s current responsibility is to just return the the initial state.

Now as your application grows you may start to create lots of reducers. What if this counter application wants to also manage a timer, forms or API calls? You will continue to add many different types of reducers, each responsible for managing specific part of state. Knowing this, we are going create a rootReducer so we can group all of our reducers into a single store. Luckily, redux has a method just for this.

Inside of our reducer directory, let’s create a file called index.js which will hold our root reducer like so:


//reducers/index.js
const redux = require(‘redux’)
const counterReducer = require(‘./counter_reducer’)
//groups our reducers
const rootReducer = redux.combineReducers({
counter: counterReducer,
});
module.exports = rootReducer

This rootReducer is what holds the entire state of our application. We need to update our `store` with with our newly created rootReducer. Lets do that:

Back in our store, we will require our rootReducer and pass it into redux.createStore()


// app/store.js
const redux = require(‘redux’)
const reducers = require(‘./reducers/index.js’) // ←- require our reducers
var store = redux.createStore(
reducers // ←- throw them in here
);
module.exports = store;

Connecting Store to Our App

Currently, our app is a bit…stale. Let’s create a quick Counter class. I’m imagining that our class will need to include some elements on the page and our newly created store.

In app/entry.js we can do something like this.

const store = require(‘./store’)
const Counter = require(‘./counter’)
function startup() {
var counterEl = document.getElementById(‘counter’)
var plus = document.getElementById(‘plus’)
var minus = document.getElementById(‘minus’)
 var myCounter = new Counter(plus, minus, counterEl, store)
}
startup();

First we import our store and yet-to-be created Counter class. We then create a function called startup which will obtain nodes from our DOM. The counter, the plus button, and minus button. We’ll then create a new counter called myCounter, and pass in our two buttons, the counter element, and our store.

Now we need to create a counter class. Here’s a base class that you should use

// app/counter.js
class Counter {
constructor(incrementEl, decrementEl, counterEl, store) {
this.incrementEl = incrementEl
this.decrementEl = decrementEl
this.counterEl = counterEl
this.store = store;
  this.currentCount = 0;
}
 updateCounter() {
this.counterEl.innerHTML = this.currentCount
}
 listenForCountChange() {
let previousState = this.currentCount,
newState = this.store.getState()
newState = newState.counter.currentCount
  if (previousState !== newState) {
this.currentCount = newState
this.updateCounter()
}
}
}
module.exports = Counter

We create a counter from three DOM elements and a Redux Store. We also have a local piece of state called this.currentCount. This will be used to set the inner HTML of the counter. We have a function called listenForCountChange() which does a variety of things.

First, it creates a variable called previousState which is a reference to the object’s currentCount property (which is 0). We then obtain our newState from the redux store with this.store.getState(). If you were to console.log(newState), you would see an object with the reducer we just created. It shows that the currentCount is zero!

Then, we compare our previousState with newState, and if they are different, we know that something in our store has changed and this state needs to be reflected in our app. We update this.currentCount to be our new state, and call a function this.updateCounter() which sets the innerHTML of our counter element with our new state. In summary, we are passing redux state to our local state only if the redux state differs from our local state.

So how do we know if this works? Let’s go back to our redux store and change the currentCount to 3:


// reducers/counter_reducer.js
var initialState = {
currentCount: 3
}

We can refresh our app and…nothing changes! Well if you had eagle eyes, you would have seen that we did not explicitly call listenForCountChange. Let’s just do that in our Counter constructor.


//app/counter.js
class Counter {
constructor(incrementEl, decrementEl, counterEl, store) {
this.incrementEl = incrementEl
this.decrementEl = decrementEl
this.counterEl = counterEl
this.store = store;
 this.currentCount = 0;
this.listenForCountChange() //←- call it here.
}

At this point, you should see that your app has changed. The counter element’s inner HTML reflects the state of our application from the redux store. You should see something like this:

Our counter showing the currentCount from Redux

Wow. Pretty neat stuff huh? I encourage you to take a little break, and go through the code a bit. See how stuff works, break it, fix it, and come back when you feel ready to work on the buttons.

Actions

When you attempt to click the buttons, nothing works. Bad programmer. Let’s create some Actions that will send messages and data to our store. With these actions, we can modify the state of our application, currentCount, to either increment or decrement the count.

Inside of app/counter.js, let’s add some event listeners to our buttons. When a button is clicked, we will dispatch an action to modify our state.


//app/counter.js
const actions = require(‘./actions/index.js’); // we’re going to create this soon.
class Counter {
constructor(incrementEl, decrementEl, counterEl, store) {
this.incrementEl = incrementEl
this.decrementEl = decrementEl
this.counterEl = counterEl
this.store = store;
  this.currentCount = 0;
  this.addEventListeners() //←- add event listeners.
  this.listenForCountChange()
}
addEventListeners() { //when a button is clicked, call either increment or decrement respectively
this.incrementEl.addEventListener(‘click’, () => {
this.incrementCounter()
})
this.decrementEl.addEventListener(‘click’, () => {
this.decrementCounter();
})
}
incrementCounter() {
this.store.dispatch(actions.incrementCounter()) //we will create this action soon
}
decrementCounter() {
this.store.dispatch(actions.decrementCounter()) //we will create this action soon
}

In order to communicate and modify the state of our application, we need to dispatch an action. We haven’t created any actions yet. Let’s create a new directory called actions and inside that a new file called index.js

An action is a function that returns an object. That’s it. The only way to call an action is with dispatch which is part of our Redux store. The object usually consists of type, as in what type of action are we dispatching, and any additional data we would like to pass to our state. So if we wanted to increment or decrement our counter by n, we could pass that in as an argument to our action. For now, let’s just keep it simple and see how actions communicate with our redux store.

//actions/index.js
const types = require(‘./action_types.js’)//we will create this soon.
var exports = module.exports = {}
//an action to increment the counter
exports.incrementCounter = () => {
return {
type: types.INCREMENT_COUNT
}
}
//an action to decrement the counter
exports.decrementCounter = () => {
return {
type: types.DECREMENT_COUNT
}
}

I like to think of actions as messengers. Any part of your application can fire any action they want, and these messengers do the heavy lifting for you. An action needs to know what kind of action it is, so in the object we create will have a single property called type. This describes the message we want to send to our reducer. Let’s create a place to hold all of our different types in actions/action_types.js. I keep my types in a separate file so that as my application grows, I or any other developer I work with can easily see all of the various types of actions that are available for use. This way, actions are not lost in different parts of the application and your team will not be stepping on each other’s feet.

//actions/action_types.js
module.exports = {
INCREMENT_COUNT: “INCREMENT_COUNT”,
DECREMENT_COUNT: “DECREMENT_COUNT”,
}

If you attempt to click those buttons, you’ll see nothing is working. This is because our reducer needs to expect the different types of actions that are available in the application. Let’s modify our `counter_reducer.js` file to expect these different types of actions.

Inside of app/reducers/counter_reducer.js


//import the various types of actions
const types = require(‘../actions/action_types’);
//set the initial count back to zero
var initialState = {
currentCount: 0
}
//counter reducer now accepts actions
var counterReducer = (state = initialState, action) => {
switch (action.type) {
case types.INCREMENT_COUNT:
return Object.assign({}, state, state.currentCount++)
case types.DECREMENT_COUNT:
return Object.assign({}, state, state.currentCount — )
default:
return state
}
}
module.exports = counterReducer

Now our reducer is a switch statement, which returns a new but modified object of our initial state every time an action is fired. If an action is dispatched but is not matched by a reducer, it returns the initial state.

Putting it all together

The last thing we need to do is subscribe to our store. Our Counter class needs to know when our state has changed, so it can reflect those changes visually. Back in app/counter.js


// app/counter.js
const actions = require(‘./actions/index.js’);
class Counter {
constructor(incrementEl, decrementEl, counterEl, store) {
this.incrementEl = incrementEl
this.decrementEl = decrementEl
this.counterEl = counterEl
this.store = store;
 this.currentCount = 0;
 this.addEventListeners()
 this.store.subscribe(this.listenForCountChange.bind(this)) //← subscribe here.
}


We took the store, or the state of our application, and had it subscribe to a function called listenForCountChange. Now whenever our store is modified, our counter class checks if the redux store’s currentCount differs from the local state of our counter. If it does, it updates the counter.

You should now have a working counter! If you don’t, you can examine the code base here to retrace your steps

This is only the basics of Redux without React. I feel that learning Redux without React first will help you understand how Redux actually works.

I encourage you to take this counter to the next level and have it increment by a value specified in an input field. The challenge is to obtain the value from an input field and pass it in as a payload property in your action. Then in your reducer, use the value from action.payload to modify your currentCount.