Primer: State management (with Redux)

Timi Ajiboye
chunks of code*
Published in
7 min readJan 17, 2018

I recently started doing React + Redux development and Redux took some getting used to. So I decided to write this to make it a bit easier for the next beginner to understand.

My aim with this is to explain the ideas behind libraries like Redux and not focus on the code implementation. As such this generalizes (somwhat) to other tools like: VueX, ReSwift (for iOS development), Reductor & Jedux (android development) and Flux (Redux & VueX are actually based on Flux).

I however have to emphasize that there are differences in how each of the above tools handles things, but because I’ve only ever really done Redux, that’s what this article is going to focus on.

Anyway, the differences will be easier to discover and understand once you fully get how one of them works. And hopefully, by the end of this, you’d be well on your way there.

What is State?

I like to think of state as an object that contains all the information about my client application in that moment.

What is state?

The above diagram attempts to explain better what state is. All the information the application needs to configure it’s UI elements is present in the state object on the right.

But this diagram only shows the state for just one view. The application state contains all the information that all the views in the entire application needs to determine what will be displayed and how it’ll display it.

Using this state object means:

  • The views in your app don’t need to care about anything but the state.
  • It is possible to reproduce a previous configuration of UI elements by just feeding it the state. So like with the diagram above, if I ever want that view to look like exactly that, I just have to make sure I somehow load that exact state object into it.
  • This makes it possible to create “application history” by saving the state every single time it changes. So you can essentially undo & redo every UI change in your application.

How do we “manage” state (with Redux)?

Well, this is not a straightforward answer. There are a couple of parts of Redux that need to be understood to fully grok the bigger picture.

Store

The store is what holds the application state. This is what all your views/components need to have access to, to be able to get the state and act accordingly. By making sure that all the components in your application can read the state from the store, the problem of passing data between components that need it no longer exists (which can be a pain in this ass when you have many complex nested components)

The store actually contains other things besides the state, but we’ll get to that soon.

Actions

Think about actions as events in your applications. An action is dispatched to send information to the store. Actions are usually dispatched when a user interacts with the user interface or when your client application makes an API call and gets a response from the server.

The only way to update the state is to dispatch actions.

User click dispatching action

Actions are pretty simple. They’re usually a Javascript object with two properties.

{
type: NEWSLETTER_FORM_SUBMIT,
payload: {email: ‘reader@chunksofco.de’}
}

The properties are pretty self explanatory, type indicates what kind of event occurred and payload contains the information that’s part of the event.

Reducers

This is where it gets a bit complicated.

Pure Functions

First of all, let us revisit what a pure function is.

  • A pure function is one that doesn’t alter (or mutate) it’s arguments.
  • A pure function isn’t dependent on any external state (like a database or global variable)
  • A pure function always returns the same output for the same input.

A simple example of a pure function is:

const pureAddition = (a, b) => a + b;

It doesn’t change the values of a or b and it will always return the same value for the same values of a & b

So…what is a reducer?

A reducer is a pure function that takes the previous state and an action as arguments and returns a new state.

The reducer(s) also exists within the store.

Relationship between the store, actions and reducers

Before we continue, let’s go over it all again.

  • The user interface/view component can read the state directly from the store. Every component has access to the store.
  • Components cannot edit the state directly. To do this, actions are dispatched to the store.
  • The store uses a reducer function to create a new state. It takes in both the previous state and the action that was dispatched as arguments.

It helps to think of the reducer as a really long pure function with a switch block.

const reducer = (previousState, action) => {
switch (action.type){
case 'NEWSLETTER_FORM_SUBMIT':
// do something
return newState;
case 'NOTIFICATION_TOGGLE':
// do something
return newState;
...
}
}

Splitting the reducer

In a large application, the state object can get really large. With many different children. It also becomes quickly apparent that a reducer can get really long. However, the great thing about reducers is that they can (and should) be split.

An application’s reducer can be split into many different smaller reducers that only handle a child of the state.

Imagine you had an application with state like this:

todos: [ 
{title: "do dishes", completed: false},
{title: "finish blog post", completed: false},
...
],
notes: [
"lorem ipsum",
...
],
cloudSync: {
lastSynced: some timestamp,
unsyncedItemsCount: 23
}

It’s possible to break up your main reducer into tiny reducers that can deal with the different parts of your state; todos, notes etc

const reducer = (previousState, action) => {  let newState = {};
newState.todos = todosReducer(previousState.todos, action);
newState.notes = notesReducer(previousState.notes, action);
newState.cloudSync = cloudSyncReducer(previousState.cloudSync, action);
return newState;
}

And in your smaller reducer like todosReducer you can have the switch block that only pays attention to the actions that have anything to do with todos.

const todosReducer = (state, action) => {
switch(action.type){
case 'ADD_TODO':
// do something
return newState;
case 'REMOVE_TODO':
// do something
return newState;
}
}

The above code isn’t how some of this stuff is implemented in the actual Redux libraries. I’m just try to explain how it all works as simply as possible.

Why must a reducer be a pure function?

First and foremost, it keeps the your application predictable. Reducers will always return the same thing if given the same argument. This makes your application easier to test.

Secondly, Redux works by taking the application state and passing it into the reducer in a loop. It expects a brand new object if there are any updates to the state, furthermore, it expects the old state if there are no required changes. It is very important that the previousState is not mutated.

All of this is important to ensure the most efficient way of updating UI components when there’s a state change.

One might initially think it makes sense to edit the previousState than to duplicate it into another variable before editing the copy. That way, all Redux needs to do is check if there are any changes in the previousState object before updating the components.

Unfortunately, in Javascript, the only way to check if two objects contain the same properties is to deep compare them. This is understandably very expensive because it has to be done many times and the state objects can be very large and deep.

However, it is possible to compare two object’s location in memory by using !==. This removes the need to check every single property within that object.

By enforcing that the previousState object/variable cannot be mutated, it makes it trivial for Redux to check if the returned object is the same old one (as a result of no changes) as it’ll have the same memory location.

As a side effect of creating a new state every time, you can “time travel” and essentially replay your application during testing and debugging.

WHY does anyone need Redux, we’ve been building client facing apps without it?

Well, you probably don’t “need” Redux. This is a good article explaining why you might not need it (and it’s written by the creator of Redux).

You don’t even need to use the Redux library to apply some of the ideas from Redux. You can just start building your version of things like I started to do in some of the examples above.

There are benefits to using Redux, some of which I mentioned earlier but it does come with a handful of tradeoffs. Here’s an excerpt from that post:

Excerpt from Dan Abramov’s post

--

--

Timi Ajiboye
chunks of code*

I make stuff, mostly things that work on computers. Building Gandalf.