This is part one of a two part series. For part two see here.
I spend a lot of time walking people through Redux best practices and how to use it, and throughout this process I've found it really helpful to dive into the source itself. While this source is very readable and well commented, I thought I'd put together a blog post to break it down and add some more context.
In this article we'll be covering
createStore. This is the function that sets up the redux store and provides access to
A note on embedded source: The most up to date redux source is available here. I'll be embedding source code as of when this post was written.
Before we get started we should reexamine the three principals of redux. Below is an excerpt from the redux docs.
Single source of truth
State is read-only
The only way to change the state is to emit an action, an object describing what happened.
Changes are made with pure functions
To specify how the state tree is transformed by actions, you write pure reducers.
The important bit here is that everything is done with pure functions. As a result of this we prevent consumers from doing things like calling
getState from within a reducer.
As we talk about
createStore we'll notice that the majority of the code enforces these constraints.
Now that we've introduced this, let's dive into
createStore. We'll start with the function signature.
This takes three arguments
The first one is hopefully straightforward. This is the
rootReducer for your application. It's a pure function that takes a
state and an
action and returns a new
state. It's most likely generated by
combineReducers which we'll cover in part 2.
This is simply the state you'd like to start with. Most likely if you're using
preloadedState you're doing one of two things. You have a server side rendered the page, or you have a data payload coming down with your source. Either way you have data you want loaded into redux as soon as the app is instantiated.
If you're not doing one of these you should probably just dispatch an action after the store is created.
Argument Validation and Enhancing
The next lines of code are mostly argument validation. You can read the full source below but here's what it's doing:
- Ensures you've passed at most three arguments
- Allow the user to only pass
enhancer. If this happens it swaps the argument order.
- Ensures the reducer is an actual function.
There is one very interesting thing going on here though. You can see it on line 22 in the gist above.
enhancer function is provided it calls it with
createStore as an argument. This function then returns a new
createStore function which only takes
This pattern is very common in the redux source, wrapping functions in other functions. In fact I like to say it's functions all the way down.
This is also how redux devtools works. It wraps the createStore function so it can inject wrappers around every exported function and listen to their inputs and outputs. We won't dive too deeply into this, but you can check out that source here.
Before we get to the function declarations,
createStore needs to set up some internal state.
Let's walk through each of these.
currentReducer- The root reducer redux calls.
currentState- A reference to the key/value object that represents state
currentListeners- This is a list of functions that have subscribed to redux.
nextListeners- This is a reference to the updated listeners before a dispatch.
isDispatching- Effectively a semaphore to block us from dispatching more than one action at a time.
If these don't make total sense yet, especially the latter three, don't worry. We'll cover these more later.
We're still inside of
createStore but now we're declaring functions that will be used throughout the lifecycle of the store. Some of these will be returned as part of the store, others will only be used internally. I'll label each as we're going.
This function seems inconsequential but it's actually very important. Redux supports pub-sub or publish-subscribe. We have no invariants around when a consumer may decide to subscribe to our store or unsubscribe from it. As a result
nextListeners needs to be mutable at all times.
Consider, a listener might even unsubscribe as a result of being called!
As a result of this we only call listeners in
currentListeners and let any new subscriptions modify
Under the hood, this works because
Array.prototype.slice returns a new reference to the list so
nextListeners point to different arrays.
This is a fan favorite, this is the function that returns
state. It get's returned as part of the store and returns a reference to the current underlying
You'll notice there's an invariant here that prevents us from calling this function during a dispatch. There's a good reason for this. When reducers are combined they only get a slice of the state, more on that in part 2. We don't want a reducer to get access to this function and derive it's state from another namespace state tree. As a result this throws if we're in the middle of a dispatch.
You may be thinking, but I call
getState from a thunk! We're specifically referring to the plain action
dispatch, not thunk
dispatch. We'll cover thunks in part 2.
The next function created is
subscribe. This is what notifies listeners that something in the store has changed. This function is returned as part of the store.
Hopefully this pattern is starting to look familiar. Subscribe takes a function that gets called every time the internal
state updates. It also does the following validation:
- Make sure the argument is actually a function.
- Make sure we're not attaching a subscribe in the middle of a dispatch.
- Make sure we can mutate listeners, otherwise we couldn't subscribe!
It also creates some internal state,
isSubscribed. This is used in the
unsubscribe function it returns.
We've covered how we start listening to things, we call
subscribe. But how do we stop? Well
subscribe conveniently returns a function called
unsubscribe that allows us to stop paying attention to redux.
This function is very similar to
subscribe. It checks
isSubscribed (internal state) to see if it's already been called, and does some validation to make sure we're not currently
Then it removes listener from the list.
You might be thinking. I've been using redux for years, I've never called these functions. That's because most integration libraries will abstract all this away for us. In the case of the react-redux library this is taken care of in the
<Provider /> component. We won't delve too far into that library but you can check out the full source here.
It's worth a reminder that all of this is still being done in the context of the
createStore function. The next function is the most important one,
dispatch. This is the thing that updates the store and notifies listeners. Let's walk through it.
Like every other function we've encountered.
dispatch starts with a bunch of validation.
You can see above it's enforcing the following rules
- We're only dispatching a plain object.
- That object has a property called
- We are not currently dispatching.
But once again you might be thinking, but I dispatch functions! This is taken care of by the thunk middleware, something to look forward to in part 2 of this article.
Now that we've made it this far, this function is probably much less impressive than you expected. Here's what it's doing.
- Set the
action. This returns a brand new state for our listeners.
- Update listeners with the next set.
- Loop through the list of listeners and call each of them.
This is also where it's important that we keep
nextListeners separate. While the reducer must be a synchronous pure function, there is no such invariant around any given listeners.
A listener may be async, take a long time, modify some global state, etc. It might even unsubscribe as a result of being called.
Two More Functions
We've now covered the bulk of the business logic in
createStore. There are now just two functions we need to mention,
The function itself seems pretty straightforward, we take a new reducer and replace the one we use in the store. Then we dispatch an action announcing this has happened.
This action will also have the "side effect" of copying over any state that the new reducer needs to know about.
This last bit I've never actually used, so I don't think I'm qualified to go into too much detail about it. Effectively it adds support for to support
observables, a proposal to ecmascript. It's just a simple wrapper around
The very last bit in
createStore is initializing the state tree and exporting functions.
This is how users get access to
In our next article we'll be covering a lot of the tooling around redux. We'll talk about
bindActionCreators, thunks, and more!
Part two is now available here!