Redux to the rescue!

John Bennett
7 min readJul 19, 2016

--

This is the second post in a series on my learnings while building a React app. See the first post.

Setting some context

The project I’ve been working on is a single-page app (SPA), with which users will be creating and managing data with a lot of complex validation rules. It’s an internal business tool, not a public-facing website.

For that reason, I’m going to completely ignore “isomorphism” — that is, a site where rendering of HTML can happen on either/both the server (in node) and the browser. You might want to do that for a consumer site/app, where the initial page load time is critical. For me, for now, it isn’t.

I’m also ignoring React Native and any issues related to running as a native app. I’m taking the React team’s word that I’ll be able to “learn once, write anywhere”.

The React community seems to really follow the YAGNI philosophy. In fact, Lance Harper commented on my first post that I should get as far as I can with plain React before adding Redux or anything else. I love that approach.

So then, why am I jumping right into Redux in this post?

Because I’m not trying to do a walkthrough or tutorial. I’m trying to describe — after the fact — the reasons why I did or did not choose to use different libraries in my React app. And to point out where I’m still not sure. As I’ve said before, I’m sure my thinking will change as I continue to learn.

Given that, I’m going to assume you have a decent grasp of React proper. If you don’t have that background, I recommend:

The Flux pattern

Later in this post, I’m going to recommend Redux. But first I’m going to quickly describe of the Flux pattern. Redux is an implementation of Flux. There are other implementations — one of them by the React team that is named Flux. Confusing, but just keep in mind that in the section, I’m describing a design pattern.

Also keep in mind that there are no dependencies in either direction between React and Flux/Redux. They grew out of the same design principles and so they go well together, but you can use either one on its own.

The “official” description of Flux is good. I recommend reading it. Here’s my take:

The Flux pattern describes data flow in an application. The golden rule is: data flows in one direction only. All changes to your app’s state are performed in one place, and then your views are notified of the changes.

The major players in the Flux pattern are:

Stores

A store is the one source of truth for state in your app: your actual data, your current UI state, everything. This is the source of truth from which all changes flow. Flux allows for multiple stores, each responsible for a single domain (in the DDD sense).

Actions

An action is created somewhere in your application — say, in a view when a user clicks a button. You can think of an action like an event or message object — it describes something that has happened or a command/request for something to happen.

An action is a simple object that, by convention, has a `type` property. It may have other properties containing relevant data. For example:

{
type: ‘CREATE_COMMENT’,
text: ‘React is cool, but so many choices to make!’,
author: ‘John’
}

The dispatcher

The action is then passed to a single, global dispatcher. The dispatcher, in turn, passes the action to each store to handle however it wants. With the example above, the store that owns comments will probably create a new object to represent the comment and add it to a list of comments on a post. Other stores might ignore it entirely.

Whenever a store makes a change to the state it owns, it publishes a notification. Your views can subscribe to these notifications, in order to update the UI for the user.

With Flux, you typically encapsulate the creation of an action in a function that then hands the action off to a global dispatcher.

function createComment(text, author) {
Dispatcher.dispatch({
type: ‘CREATE_COMMENT’,
text: text,
author: author
})
}

Functions like createComment() should be the only way to change the state of your app. You can put any views/UI you want on top of them — like, say, a tree of React components.

Summing up the Flux pattern

So the Flux pattern gives you one-way data flow:

action -> dispatcher -> store -> view

User actions in your views are often the trigger for actions to be created and dispatched, so the flow is really:

view -> action -> dispatcher -> store -> view

Yes, it goes in circles — but only in one direction! The important thing is that views never directly update application state in the store. Only dispatched actions result in state changes.

React is based on the idea that your UI should be purely a function of application state — state in, UI out — so React and the Flux pattern go hand-in-hand.

The Flux implementation

As I was first reading about the Flux pattern, I felt right at home. In the late 2000s, I spent several years building a system where all interprocess communication happened asynchronously on a pub/sub message bus (on the server side). One-way, “fire-and-forget” command messages that result in some later event message(s) saying “something changed” are very comfortable concept for me.

But as I started looking at the Flux implementation, I was a little confused. I understand having multiple stores — each owning one part of the state.

But what is this waitFor() method? A store calls waitFor() because it knows that some other store has to update first? Doesn’t that break the rule that each store independently owns its own state? Why would there be dependencies? And really, what’s with all the switch statements? :)

It all just seemed icky. My first impulse was to create my own implementation of the Flux pattern. (See, I really am becoming a JavaScript developer!)

Redux to the rescue

Fortunately, Redux came to the rescue before I got very far down that road.

The Redux site is great. I highly recommend reading through the intro and watching the Getting Started with Redux videos (~2 hours in total) made by Redux’s creator, Dan Abramov. They are worth the time.

Once again, Cory House’s Pluralsight course on React and Redux is a great resource, as well.

The things I like about Redux are:

Actions are still represented as simple objects, and there is still a singleton dispatcher. You interact with it solely through the dispatch() method.

Redux encourages separating the creation of action objects from dispatching them. By convention, you write action creator functions that create and return an action object.

You would still write a createComment() function, as above, but with Redux it just returns the action. Why? Sometimes creating an action is not as trivial as my example. This way, you can test creating the action independently from dispatching it. So your code would look like:

dispatch(createComment(‘Testing FTW!’, ‘John’))

In Redux, there is only a single store that holds all of the state. It’s just a plain JS object. (Or not. More on that in future posts.)

Where Flux has multiple stores, Redux divides up the responsibility of updating parts — or subtrees — of the single store. For example, if your state for a blog looked like this:

{
user: { isAuthenticated: true, username: ‘john’ },
displayPrefs: { theme: ‘dark’, openLinksInNewTab: true },
blogPosts: [
{
id: 1,
title: ‘Spelunking in the React ecosystem’,
comments: [
{ id: 52, value: 'Nice post!', author: 'Lance' }
]
},
{
id: 2,
title: ‘Redux to the rescue!’
},
]
}

You would have three top-lever reducers, one corresponding to each top-level property in state: user, displayPrefs, and blogPosts. Each would only be able to see its part of the state tree. Reducers can be nested, as well. In this example, you’d probably have a separate reducer responsible for comments. The blogPosts reducer would delegate to the comments reducer, passing it just the comment list.

A reducer is just a function that takes the current state and an action, and returns a new state. Reducers don’t mutate the state. This immutability helps avoid bugs and makes reducers very easy to test.

myReducer = function(state, action) {
// … do stuff
return newState // the original state is unmodified
}

The immutability of state also helps Redux to have excellent dev tools. You can run them as a Chrome extension, or include them directly in your project with the redux-devtools package. They let you see every dispatched action and the resulting state changes (if any). You can even “time travel” — see what your app would look like if one or more of those actions didn’t happen.

Redux encourages the React best practice of making most components pure functions and passing state down through your component tree as props. When you do need a component in the middle of your hierarchy to react to state changes, the react-redux package provides just the little glue you need, in a very nice, clean way.

Redux is simple and clear, makes testing easy and has great dev experience. The data flow is still:

view -> action -> dispatcher -> reducer -> view

What’s not to like?

Well, reducers do still have all those switch statements on the action types. Redux opts for a little bit of boilerplate code in order to avoid being too magical. That’s great for clarity, and you can easily add a little magic, if you want to avoid using switch.

Coming up…

As your Redux-based app grows in complexity, you’ll likely be faced with new choices and questions:

  • Should I use a package like immutable or icepick for my state, or just trust everyone not to mutate it directly?
  • How should I structure my action objects?
  • How do I work with user input and forms? Should all input state changes — literally every keystroke — flow through Redux?

I’ll get into those questions in future posts.

--

--

John Bennett

Principal at Nevo.com. Software development, lean product development, continuous delivery, devops. New Yorker & Rhode Islander.