Reading Redux: createStore

Joshua Kelly
Universe Engineering
4 min readMay 1, 2017

--

An essential skill every developer must learn is reading the source. By the reading source, we eliminate FUD (Fear, Uncertainty, Doubt) about our tools. This is especially critical as teams grow in size, as folklore explanations calcify and shortcut deep dives.

The root export of the Redux library.

Many of us have worked on projects whose dependencies undergo a kind of “community rot”. Yes, our code still works, builds still run, tests still pass, but public documentation of our dependencies starts to disappear. Soon enough, we’re left a major version of two behind without anything but… the source.

So being comfortable with navigating the source of our tools becomes critical.

Let’s apply that idea and try to read through the popular state management tool, Redux.

Redux library itself is only a set of helpers to “mount” reducers to a single global store object.

Redux is composed of the following modules: createStore, combineReducers, bindActionCreators, applyMiddleware, and compose. We’re going to narrow in on createStore today, and cover the others in subsequent followups.

createStore

This module exports a function of the same name by default.

The createStore function accepts a reducer, preloadedState, and an enhancer.

We call the value returned by the createStore function a Store. As you can see, Stores have a number of properties: dispatch, subscribe, getState, and replaceReducer (plus the private $$observable).

At a high level, that’s the entire structure of this module.

(reducer, preloadedState, enhancer)

To start, createStore accepts a reducer, and we see it applies a typeof check to ensure that this parameter is a function: https://github.com/reactjs/redux/blob/552f1f84ab803d19bd79a785b2c89d94f11c405c/src/createStore.js#L53-L55

The inline docs describe a reducer as:

A function that returns the next state tree, given the current state tree and the action to handle.

And we see this reflected in the flow types provided by the distribution at redux/flow-types/redux.js:

The Flow definition for a Reducer

To synthesize: the reducer we pass to createStore is a function that takes the current state and action, and the transforms them into the next state. We’ll return to the details of the action parameter under #dispatch.

But already, we know enough to be able to make a minimal Redux app:

That’s it. Critically, notice that we can run this in contexts outside of a browser, like in the Node REPL (using the commented out CommonJS version of the module import). Our app object is working — but useless — Store.

What about preloadedState and enhancer?

preloadedState The initial state. You may optionally specify it to hydrate the state from the server in universal apps, or to restore a previously serialized user session.

enhancer The store enhancer. You may optionally specify it to enhance the store with third-party capabilities such as middleware, time travel, persistence, etc. The only store enhancer that ships with Redux is `applyMiddleware()`.

Let’s add a preloadedState:

In this example, the initial starting state of the app is simply { booted_at: new Date() }. The reducer we pass as the first argument simply returns the existing state, no matter what action is passed to it.

We’ll return to include an enhancer to this example later.

As mentioned, the return value of createStore is an object with a number of functions as properties: getState, dispatch, subscribe, replaceReducer.

#getState

This function returns the currentState — a value first set to the value of preloadedState. That’s how the initial state bootstrapping works.

Simple.

#dispatch

#dispatch invokes two conditions on its parameter: an Object type check, and a value of anything but undefined for the property type. So basically, dispatch must be called with an object passing a type prop — an object which it returns. This is captured in the Flow type provided by the project:

What other constraints does Redux impose on actions? None! They can have any other data. “Uh, won’t that get messy?” Maybe. The flux-standard-action project has recommendations on how you might structure actions inside of an application.

The function otherwise consists of two critical operations: (1) calling the reducer with the currentState and action then assign the return to currentState, and (2) invoking all of the listeners after the reducer has been called.

#subscribe

First, do a function type check on the listener parameter. Set a value isSubscribed to true, and then push the listener function onto the existing array of nextListeners. Return a function, unsubscribe, which removes the listener from the nextListeners array. Recall that the listeners are actually invoked at the end of the dispatch call. In effect, every dispatch ends with all of the listeners being invoked. Ultimately, the react-redux bridge is an abstraction built on extending dispatch listeners.

Putting this together, we can understand everything that’s going on in the example calculator app from Redux’s own README:

  1. Importing the createStore function
  2. Defining a reducer function (counter) which accepts the state, an action Object with a type property, and transforms those inputs into the next state
  3. Calling createStore with the reducer
  4. Passing a listener to subscribe, to log the state on each dispatch
  5. Calling dispatch with different values for the type property in the action object

This all seems less daunting than it did initially, right? Redux is a clean, simple state management library with highly readable modern JavaScript. Who’d’ve thought?

We only need to cover a few other modules before we’ve finished reading through Redux: combineReducers, bindActionCreators, applyMiddleware. We’ll return to these soon!

--

--