Skip to content

Instantly share code, notes, and snippets.

@jcgertig
Last active September 23, 2016 20:10
Show Gist options
  • Save jcgertig/1e2706abdbc79d9f28a37faaf00164f0 to your computer and use it in GitHub Desktop.
Save jcgertig/1e2706abdbc79d9f28a37faaf00164f0 to your computer and use it in GitHub Desktop.
Redux

Redux

Redux, according to the docs, is "a predictable state container for JavaScript apps." It's a very lightweight (2kB) implementation of flux. If you haven't heard of flux before, that's fine.

Basically, flux is like a sedan. It's a concept with many agreed-upon characteristics associated with it, but it's not, itself, a tangible thing. Just as there's no single definitive "Sedan" that you can go out and buy, there's no definitive "Flux" that you can download and install. Instead, you choose one specific implementation of it. I don't know much about cars, but whatever the most minimal, stripped down, yet highly performant sedan is, that's Redux.

The basic idea behind flux is, you have a dispatcher that sends actions to the data store, and updates the view. It's an overall data flow architecture, where -- unlike the two-way data binding of MVCs -- updates only go one way. If you've ever been in getter/setter hell trying to track what view is touching what model, you can understand how a one-way data flow might be beneficial.

With all that said, here's a helpful diagram from Facebook's Flux documentation:

Flux

One huge implication of one-way (or unidirectional) data flow is that your data store is not touched by your view. At all.

Redux is one particular implementation of a subset of flux concepts. The main differences between Redux and full flux implementations are:

  • There are no discrete dispatchers in Redux; your store listens directly for actions, and uses a function called a reducer (more on this later) to return a new app state each time an action is dispatched.
  • Redux holds your entire application's state in one place.
  • Your app's state is immutable.

Consider your app state as a tree, with a root and child nodes. If you want to make a change to a child node, you can't change it directly because state is immutable, so you make a copy of the target node and apply your changes. You also make a copy of the target node's parent, because it wouldn't make sense to have one parent produce two versions of the exact same node. Maybe you append a child node to the new node, too. That's fine. At the end of all this, you just need to attach the existing, unchanged nodes to your new ones. Like this:

Nodes

The best part is, the original state (the blue one) still exists. The unchanged blue nodes are not re-rendered in the new state, just the orange nodes. This makes debugging really easy because you can isolate the action that caused the state change, and go back and forth at any time. Redux dev tools also happen to be unusually good (see the next section for an example), and, combined with React dev tools, have basically spoiled me for life.

Another implication of this design pattern: You really cut down on the number of dependent asynchronous operations in your data flow. Think of a room with a table and sofa and you need to switch them around. To do so you would: "Move the table. Wait for the table to be out of the way, then move sofa..."

Contrast that to the declarative statement: "Sofa and table are positioned at (fill in the x,y coordinates here)." The end. No waiting to clear the way first.

Intuitively, this makes sense. If you want a room with a sofa and table in it, positioned in a certain way, then why should it matter the order in which items are placed? This straightforwardness is what really won me over -- it just feels right to me.

How Does Redux Work?

Redux is astonishingly simple. It uses a function called a reducer (a name derived from the JavaScript reduce method) that takes two parameters: An action, and the current state.

The reducer has access to the current (soon to be previous) state, applies the given action to that state, and returns the desired next state.

Reducers are designed to be pure functions; meaning, they produce no side effects. If you pass the same input values to a reducer 100 times, you will get the exact same output value 100 times. Nothing weird happens. They are completely predictable. As someone with a prominent "NO SURPRISES" sticky note on my monitor, this is a wonderful idea to contemplate.

Reducers do not store state, and they do NOT mutate state. They are passed state, and they return state. This is what reducers look like in action (did I mention that the dev tools are awesome?):

Console

The above is the console after two actions: Fetch Friend, followed by Save Gifts. The first action passed FETCH_FRIEND to the reducer with an updated friend object that is returned in the next state. The second action passed SAVE_GIFTS to the reducer, along with an array of 4 gifts. The next state reflects this updated gift array. As usual, you can see exactly what existed in the state before and after this action occurred by simply inspecting each object.

Note that the reducer is passed only the slice of state that requires updating (the friend object and the gifts array, respectively), and it returns a new state after each action is dispatched.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment