Enter Your Email Address to Watch This Lesson

Your link to unlock this lesson will be sent to this email address.

Unlock this lesson and all 1046 of the free egghead.io lessons, plus get JavaScript content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

Redux: Implementing Store from Scratch

2:28 JavaScript lesson by

Learn how to build a reasonable approximation of the Redux Store in 20 lines. No magic!

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

Learn how to build a reasonable approximation of the Redux Store in 20 lines. No magic!

Avatar
Steve

I'm curious why listeners aren't called with the current state? What is the advantage of the listeners calling getState() themselves?

In reply to egghead.io
Avatar
Dan Abramov

Thanks, it's a great question. The main reason is to keep the API orthogonal. Basically, same reason why React components don't receive props as argument to render(). There should be just one obvious way to do something.

For example, in Rx, subscribers receive new value like you described. But in Rx there is no notion of "current" state. So argument is the only way to receive current value. In our case, however, getState() exists, and this is why we don't pass it as argument.

If this sounds unconvincing consider that subscribe is a low level API. Usually you'd use connect from React Redux or a custom helper. Why? It's usually not enough to just receive the current state. You also want to have the previous state so you can compare whether the part you're interested in has changed. In fact you might want to write a function like subscribeTo(store, selectState, onChange) that encapsulates it. At this point it doesn't matter that you don't receive an argument in the low level subscribe API because you created a custom function and you can do it in your API just fine.

More practically we do this because there are wrappers around Redux Store that may change its behavior. For example, Redux DevTools wraps both reducer and getState. If the subscriber received the current state, such "store enhancers" would also have to override subscribe which is fragile. Orthogonal APIs are much simpler to extend!

In reply to Steve
Avatar
Steve

Dan,
Thanks for the detailed reply. I guess I still don't see how the signature of the subscribe function affects the signature of the subscriber/callback/listener function? If, for example, the convention for calling all callback/listener functions was with the action, previous state and new state the subscribe function would stay the same:

const render = (state, action, previous_state) => {
document.body.innerText = state;
};
store.subscribe(render);

I see how I could write my own higher level subscription function like subscribeTo() to filter based on content of the new state by supplying a filtering callback function via the subscribe primitive (although I don't yet see how I would get access to prior state since the dispatch() function appears to call the listener/callback after the state change; but maybe you'll cover that in later videos!).

I'm looking forward to the future videos; so I can see how I can use the Redux API as you intended.

In reply to Dan Abramov
Avatar
Dan Abramov

If you’re interested in why it’s best to keep subscriber signature argumentless, please check out Redux DevTools implementation, and how it overrides getState() method on the store. It’s hard to continue the discussion without that. ;-)

In reply to Steve
Avatar
Steve

At first, looking at that code I couldn't follow the lifting/unlifting concepts and the multiple levels of functions being returned. But I came across the middleware docs: http://rackt.org/redux/docs/api/applyMiddleware.html which helped explain the design. I've read that doc multiple times and I'm starting to follow the implementation.

I'm used to middleware implementations where the action and state are passed along (or not) and/or extended/modified on the way through the "chain" of middleware. For example this is the Django middleware model where middleware can access/modify the request(action) and response (state): https://docs.djangoproject.com/en/1.9/topics/http/middleware/#hooks-and-application-order.

To my eye it appears to be functionally equivalent to the redux middleware mechanism(?). In the Django model, it is easy to write middleware by implementing a function accepting a request or response and reacting to/modifying/stopping/delaying it. So Django uses a convention of supplying the (possibly modified) action/state to each middleware in the chain w/o an equivalent to redux's getState().

As I mentioned previously, it isn't my intent to argue about the model you've chosen for redux. But I hope you can see from my previous experience why I asked my question. Now that I've found the middleware docs I see how you thought about the problem.

In reply to Dan Abramov
Avatar
Lucas

Hello, i didn't understand why needed to return the function that remove de subscriber listener of listeners array.

Avatar
Negin

Hi,

I have a very basic and fundamental question.

Why do we need to implement createStore while Redux provide it to us?

In which circumstances we need to create our own store and in which situation we can use Redux store?

Avatar
Sammy Gonzales-Luna

I too was confused by this aspect of the createStore implementation. If you listen close, Dan mentions that the purpose of the return statement is to avoid having a separately defined method to UN-subscribe a listener. Therefore, the return statement returns an anonymous function that takes a listener and removes that listener from the listeners array.

In reply to Lucas
Avatar
Justin

He is just showing how it works by providing a simplified example of what goes on "behind the scenes."

In reply to Negin
Avatar
Vadim

And I want to extend your great answer with one thing, because it may be not obvious for everyone. We need to save store.subscribe(render) to a variable, which we will call with parenthesis for unsubscribe. It may seems like:

//For subscribe
let renderSubscription = store.subscribe(render);
//For unsubscribe
renderSubscription();
In reply to Sammy Gonzales-Luna
Avatar
aaronisme

I got a question about how to implement the unsubscribe here, why we are not providing an dedicated method to do it?

In the previous video we looked at how to implement a simple counter example using the createStore function provided by Redux and the store object it returns that provides the getState method to get the current application state, the dispatch method, to change the current application state by dispatching an action, and the subscribe method to subscribe to the changes and re-render our application with the current state of the app.

If you're like me you prefer to understand the tools that you're using. In this tutorial we're going to re-implement the createStore function provided by Redux from scratch. The first and the only form of what we know so far argument to the createStore function is the reducer function provided by the application.

We know that the store holds the current state. We keep it in a variable, and the getState function is going to return the current value of that variable. This function, combined with the dispatch function and a subscribe function on a single object is what we call the Redux store.

const createStore = (reducer) => {
  let state;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {

  };
  const subscribe = (listener) => {

  };
  return { getState, dispatch, subscribe };
};

Because the subscribe function can be called many times, we need to keep track of all the changed listeners. Any time it is called we want to push the new listener into the array. Dispatching an action is the only way to change the internal state.

const subscribe = (listener) => {
  listeners.push(listener);
};

In order to calculate the new state we call the reducer with the current state and the action being dispatched. After the state was updated, we need to notify every changed listener, by calling it.

const dispatch = (action) => {
  state = reducer(state, action);
  listeners.forEach(listener => listener());
};

There is an important missing piece here. We haven't provided a way to unsubscribe a listener. Instead of adding a dedicated unsubscribe method, we'll just return a function from the subscribe method that removes this listener from the listeners array.

const subscribe = (listener) => {
  listeners.push(listener);
  return () => {
    listeners = listeners.filter(l => l !== listener);
  };
};

Finally, by the time the store is returned we wanted to have the initial state populated. We're going to dispatch a dummy action just to get the reducer to return the initial value.

dispatch({}); // 5

This implementation of the Redux store apart from a few minor details and edge cases, is the createStore shipped with Redux.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?