Every once in a while something new comes along that just feels right.
When this happens, I get a tingly feeling in my left pinky toe. That’s how I know it’s not just a fad. It’s how I know it’s legit.
My pinky toe has a pretty decent track record:
- It first tingled when I moved an element on the page with jQuery.
- It tingled when Backbone.js came out. I blogged about it in 2010
- It tingled when I pushed my first message from server to client with Socket.io and Node.js
- It tingled with React after I got over “JSX shock”
- It’s been tingling for a few weeks straight now about a thing called Redux.
Redux, you say?!
Yes. As in Dan Abramov’s Flux-inspired application-state management thingamabobber.
It’s so young! How can you know?!
Yeah, I can’t. It’s so young that it doesn’t even have its own website yet and poor Dan has barely had time to start writing docs yet.
He’s been scrambling to throw together gists showing patterns to those of us who have been trying to build stuff with it.
Despite all that it’s gathering stars on GitHub like… something that gathers a lot of stars.
So what’s the big deal?
Application state in Native Web Apps (a.k.a. Single Page Apps) is hard.
When I say “state” I mean not only the data coming from an API, but also all the UI state in an application. It’s state that determines which navigation item is “active”, which view is currently showing, even the current url is just another piece of state.
In order to be able to build complex applications we have to have ways of abstracting and dealing with state outside of just putting in the DOM and reading from the DOM.
What I mean is, if you’ve got code like this anywhere in your app:
if ($(element).hasClass('active')) {
// do something
}
You’re reading state from the DOM.
It works for simple stuff, but simply doesn’t scale well.
As your app grows, if you’re storing state in the DOM you’ll likely make a mess.
This is why we have MVC, right?
Well, yes. But at the point where you can relatively inexpensively re-render at will. All of a sudden we need a lot less from our M and C.
Redux is only concerned with state
It doesn’t help you render stuff, it doesn’t tell you how to do routing, etc. It’s just about maintaining application state.
It weighs practically nothing. When I add it as a dependency to an existing app and bundle up the app it only increased the final file size by all of 2k!
When you consider what it does for you when you adopt its patterns and the code you can remove as a result, likely it will have end up having a decent net decrease in the file size of your app.
Abstracting state
Historically, we’ve dealt with the difficulty of state by trying to model it as a set of observable objects (Models) and observable arrays of those models (Collections).
This gets us quite a bit further than simply keeping state in the DOM. Now we can track our application state as a tree of observable objects. Our views can listen to changes to various pieces of that state and update the DOM accordingly.
That’s all fine and good. You can clearly build very complex applications using this pattern.
I have done that, other people have done that, it’s very doable.
But… is it enough?
Maybe not.
Why not? The short answer is: It’s still very easy to make a mess of your app.
You’ve got events firing everywhere with unknown side-effects. Pretty soon, it’s hard to trace errors.
In addition, by introducing all these observable objects we’ve also dramatically increased the API surface area of the code. In other words, there’s so much more that a developer has to understand before they can work on the app. They have to understand how to observe your models, how they fire events, etc.
None of these features really exist in JavaScript itself. So all of a sudden, knowing JavaScript itself isn’t good enough you also have to have a deep understanding of the observables you’re using.
Performance
Another issue is, adding all this “observability” code also involves adding a bunch of code. We’ve learned that large JS payloads come with a potentially severe performance penalty.
Also, constantly checking whether everything’s changed all the time simply isn’t efficient.
Plus, creating all the custom objects all the time, isn’t without cost. It takes more processing, it takes more memory.
Simplest possible comparison in JS
As it turns out, the simplest/fastest way to check whether an object has changed is to check whether it’s the same object reference or not.
Instead of doing some sort of deep comparison of properties such as:
_.isEqual(object1, object2)
It’d be a lot faster/simpler if we knew that any time an object changed, we replaced it, instead of editing it in place.
Then our check can be simplified to just this:
object1 === object2
That’s the basic idea of “immutability”. It’s not necessarily that you can’t change the object (I know I was confused by this). We can implement enforced immutability with tools like Immutable.js. Or even React.addons.update.
But you don’t need tools for it.
You can just follow the immutability rule: “If you change it, replace it.”
It’s a bit more arduous in ES5, but with the new features being added to the language, it becomes pretty simple.
Rather than doing:
var obj = {
something: 'some value'
}
// here we’re just editing `obj` in place
obj.something = 'some other value'
You can do it like this:
let obj = {
something: 'some value'
}
// this creates a brand new copy overwriting just that key
obj = {...obj, something: 'some other value'}
We can do the same with arrays of objects, rather than editing them in place:
var myStuff = [
{name: 'henrik'}
]
// push modifies the array defined above
myStuff.push({name: 'js lovin fool'})
You can replace the array like this:
let myStuff = [
{name: 'henrik'}
]
myStuff = [...mystuff, {name: 'js lovin fool'}]
The difference is, now our views can super efficiently determine if they need to update themselves.
In React such a method exists:
shouldComponentUpdate (newProps) {
return newProps.obj !== this.props.obj
}
Ok, so what?
Did I mention it’s mostly just a simple implementation of a pattern, that it’s super flexible, that it’s tiny, and that it has a very small API surface to learn. These are a few of my favorite things 🎶.
In addition, the way it’s structured lends itself very nicely to rendering the first payload on the server to build Universal (a.k.a. Isomorphic) apps.
How is this compared to Flux?
Flux is just a pattern. I’d say it’s a good one, generally.
But most Flux implementations I’ve seen don’t get me excited. There’s a ton of new verbiage and concepts but it doesn’t really feel like it makes anything dramatically simpler.
Redux is definitely inspired by Flux. But it’s not a pure Flux implementation. Biggest difference is that it uses a single store that wraps a state object containing all the state for your app. Then instead of writing a bunch of stores, you as a developer write reducers for it…
So how do I actually use it?
Things to understand:
-
Everything that happens in your app is an “action”. These can be caused by users, browser events, or server events. Doesn’t matter. Everything that changes something in your app does it via an “action”.
-
You have one giant state object that represents all the state in your app. These are not special Models, or Collections, it’s just friggin’ objects, arrays, and primitives. No magic.
-
You write “reducers” for everything that changes state. Are you familiar with the
[].reduce()
method of an array? If not, go read this. But areduce
function gets a starting state, the current value and returns the new state. That’s exactly what we want to do in response to actions. We get the starting state, the current action, and we return the new state.
So what does this all mean?
Actions are all sequential.
You can re-produce exact current state of the app at any point, by having the starting state, and re-playing the actions that occurred.
Your app becomes a starting state and a sequence of actions.
This means you can do amazing things like log out all the actions, and then step backwards in time, and replay them however you want.
Mix that with the ability to “hot-load” or auto-update your (V)iew components and your reducer functions without re-loading the page, and you have a new and improved kind of magic. The kind that feels like magic because of the power it gives you, but isn’t actually very hard to wrap your head around.
Also, it’s important to understand that Redux isn’t in any way tied to React. You can use Redux with whatever view layer you want. It’s a nice conceptual fit with React, but certainly doesn’t require it.
So should I drop everything and build apps with Redux?
No. Docs are still being written, Dan is desperately trying to get a 1.0 shipped, the devtools will add a lot of value but they’re not very complete at the moment.
But watch this space! I built and deployed a simple app with it already and… yeah, going to do a lot more of this.
Basically, for whatever it’s worth, this is me going on record saying this is going to be a big deal and I think this is where the web should go next.
This is something I’m very excited about and as a rather even-keeled Swedish guy, it takes a lot to get me excited.
Disagree? I’m on the twitters as @HenrikJoreteg. Explain (kindly) how I’m wrong :)
More info
- Dan’s awesome introductory talk from React Europe
- There’s a #redux channel on the Reactiflux Slack where you can get help.
- It’s possible to run the Redux devtools as a Chrome extension which is where I think this needs to go, ultimately.
- The in-progress Redux docs
- The main Redux repo
- Dan Abramov on Twitter