Skip to content

Instantly share code, notes, and snippets.

@tmkelly28
Created April 12, 2017 14:53
Show Gist options
  • Save tmkelly28/7a38351a142f2bbe8d791b3239c96623 to your computer and use it in GitHub Desktop.
Save tmkelly28/7a38351a142f2bbe8d791b3239c96623 to your computer and use it in GitHub Desktop.

Part 3 - The Store (con't)

Here's where we left off:

const reducer = toyCarReducer; // pretend that this is the reducer for the toy car we just wrote!

function createStore (reducer) {

  let currentState = {};
  let reducer = reducer;

  function Store () {}
  Store.prototype.getState = function () {
    return currentState;
  };
  Store.prototype.dispatch = function () {};
  Store.prototype.subscribe = function () {};

  return new Store();
}

const store = createStore(toyCarReducer); // creates a store that uses our toyCarReducer

In the last section, we saw how we can use the reducer function we wrote to generate a new state object. We just needed pass in the previous state and an action. Now that the store has both our reducer and our currentState. We just need a way to pass actions to our reducer. This is what store.dispatch does.

const reducer = toyCarReducer;

function createStore (reducer) {

  let currentState = {};
  let reducer = reducer;

  function Store () {}
  Store.prototype.getState = function () {
    return currentState;
  };
  Store.prototype.dispatch = function (action) {
    currentState = reducer(currentState, action); // invoking store.dispatch "resets" our currentState to be the result of invoking the reducer!
  };
  Store.prototype.subscribe = function () {};

  return new Store();
}

const store = createStore(toyCarReducer);

Also, remember that if we invoke the reducer with undefined as the first argument, it will give us our initial state! The createStore function expects you to write reducer functions that do this. As long as we follow the rules for how a reducer function should work, this will cause the first value of "currentState" to be our initial state.

const reducer = toyCarReducer;

function createStore (reducer) {

  // our first "currentState" is the result of invoking the reducer with undefined, and a dummy action
  let currentState = reducer(undefined, {});
  let reducer = reducer;

  function Store () {}
  Store.prototype.getState = function () {
    return currentState;
  };
  Store.prototype.dispatch = function (action) {
    currentState = reducer(currentState, action);
  };
  Store.prototype.subscribe = function () {};

  return new Store();
}

const store = createStore(toyCarReducer);

const initialState = store.getState() // { y: 0, x: 0 }
store.dispatch({ type: "FORWARD" });
const state1 = store.getState() // { y: 1, x: 0 }

When we store.dispatch an action, we invoke our reducer with the currentState and the action, and reassign currentState to be the result.

This works great! It would be nice to know when the state changes though. That way, if we need to do something every time our state changes (like update our UI), we can deal with it. This is exactly what store.subscribe is for.

store.subscribe takes a callback function we want to invoke every time the state is changed. We can make as many "subscriptions" as we like - it stores them in another "private" array.

const reducer = toyCarReducer;

function createStore (reducer) {

  let currentState = reducer(undefined, {});
  let reducer = reducer;
  let listeners = []; // set aside a listeners array that we have access to via closure

  function Store () {}
  Store.prototype.getState = function () {
    return currentState;
  };
  Store.prototype.dispatch = function (action) {
    currentState = reducer(currentState, action);
  };
  Store.prototype.subscribe = function (callback) { // store.subscribe takes a callback...
    listeners.push(callback);                       // and pushes it in the listeners array
  };

  return new Store();
}

Whenever we're done changing our state, we want to invoke all of these listeners. We can bake this right into store.dispatch.

const reducer = toyCarReducer;

function createStore (reducer) {

  let currentState = reducer(undefined, {});
  let reducer = reducer;
  let listeners = [];

  function Store () {}
  Store.prototype.getState = function () {
    return currentState;
  };
  Store.prototype.dispatch = function (action) {
    currentState = reducer(currentState, action); // first we update the state...
    listeners.forEach(callback => callback()); // ...then we invoke all the listeners!
  };
  Store.prototype.subscribe = function (callback) {
    listeners.push(callback);
  };

  return new Store();
}


const store = createStore(toyCarReducer);

// we "subscribe" a callback function
store.subscribe(() => console.log('Hey, the state changed to be: ', store.getState()));

store.dispatch({ type: "FORWARD" }); // we would see "Hey, the state changed to be: { y: 1, x: 0 }" logged to the console

Sometimes we might want to cancel our subscription. To help us to this, store.subscribe returns a function. When we invoke that function, it will remove the callback from the listeners array. This is similar to how setInterval gives us an interval id back, which we can then pass to clearInterval.

const reducer = toyCarReducer;

function createStore (reducer) {

  let currentState = reducer(undefined, {});
  let reducer = reducer;
  let listeners = [];

  function Store () {}
  Store.prototype.getState = function () {
    return currentState;
  };
  Store.prototype.dispatch = function (action) {
    currentState = reducer(currentState, action);
    listeners.forEach(callback => callback());
  };
  Store.prototype.subscribe = function (callback) {
    listeners.push(callback);
    // store.subscribe now returns a function
    return function () {
      // all this function does is remove the callback we passed in from the listeners array!
      listeners = listeners.filter(cb => cb !== callback);
    }
  };

  return new Store();
}


const store = createStore(toyCarReducer);

// we "subscribe" a callback function, AND store the function we get back in a variable called "unsubscribe"
const unsubscribe = store.subscribe(() => console.log('Hey, the state changed to be: ', store.getState()));

store.dispatch({ type: "FORWARD" }); // we would see "Hey, the state changed to be: { y: 1, x: 0 }" logged to the console

unsubscribe(); // invoking the function we stored in "unsubscribe" remove the subscription!

store.dispatch({ type: "FORWARD" }); // nothing logs to the console this time! We removed that listener!

And that's it! That's all there is to Redux! (Well, okay, the real Redux has a few more features, but this is fundamentally how a Redux store operates).

Here's our final result:

const reducer = toyCarReducer;

function createStore (reducer) {

  let currentState = reducer(undefined, {});
  let reducer = reducer;
  let listeners = [];

  function Store () {}
  Store.prototype.getState = function () {
    return currentState;
  };
  Store.prototype.dispatch = function (action) {
    currentState = reducer(currentState, action);
    listeners.forEach(callback => callback());
  };
  Store.prototype.subscribe = function (callback) {
    listeners.push(callback);
    return function () {
      listeners = listeners.filter(cb => cb !== callback);
    }
  };

  return new Store();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment