Video Course

Build an Interactive JavaScript Food Menu: Lesson 9 of 14

Setting Up a Central State

Up Next

Showing Menu Using the Store

Autoplaying in 7 seconds!

Cancel

To make thinking about the flow of our application easier, we are going to create a simple storage object that will hold all parts of our state. We will be able to trigger changes to the store while interacting with the page. In addition, we will be able to listen for these events to occur and change the page to account for this.

First, we will create a factory function called createStore that will create an empty store object for us. Add the following to a file at src/js/state.js.

const defaultState = {
  items: {},
  cart: (new Set()),
  cartVisible: false,
};

export function createStore(reducer) {
  const listeners = {};
  let state = Object.assign({}, defaultState);

  function on(event, cb) {
    if (!listeners[event]) {
      listeners[event] = [];
    }

    listeners[event].push(cb);
  }

  function trigger(event, data = {}) {
    state = reducer(state, event, data);

    if (listeners[event]) {
      listeners[event].forEach(cb => {
        cb(state);
      });
    }
  }

  return {
    on,
    trigger,
  };
}

We created the defaultState, which is the starting place for our application's state. It's essentially just the layout of our state. There isn't any actual data in it yet. We also created a function that, when called, will return an object. This object has two methods on it: an on method and a trigger method. The on method registers a callback with the store that will be called whenever this event is triggered on the store. trigger fires an event on the store. It also takes some optional data that is passed to the reducer which we will create in a moment. The reducer spits out the new state. All the callbacks registered for that event are then called and the state is passed to each of them.

Now that we have a function to create a store, let's actually create it. In the index.js file, put the following code.

import app from './components/app';
import { createStore } from './state';

function reducer(state, event, data) {
  switch (event) {
    case 'FOO':
      return Object.assign({}, state, {
        name: 'alex',
      });
    case 'BAR':
      return Object.assign({}, state, data);
    default:
      return state;
  }
}

const store = createStore(reducer);

store.on('FOO', state => {
  console.log('in FOO callback');
  console.log('state', state);
});

store.on('BAR', state => {
  console.log('in BAR callback');
  console.log('state', state);
});

store.on('NOT_REAL', state => {
  console.log('in NOT_REAL callback');
  console.log('state', state);
});

store.trigger('BAR', {
  name: 'frank',
  job: 'web developer',
});
store.trigger('FOO');
store.trigger('NOT_REAL');

fetch('food.json')
  .then(res => res.json())
  .then(resBody => {
    const body = document.querySelector('body');
    body.insertBefore(app(resBody), body.childNodes[0]);
  });

First, we imported the createStore method we created so we can use it. Next we created a reducer. The reducer takes in the current state, the event that was triggered, and any data that was passed to the trigger method. Here is where we define how the state should change when certain events fire. I have filled in some sample ones for us to see how it works. Notice, if the event is not in the reducer, then the current state is just passed back.

Next we set up some listeners to be called so we can see the data change. Then we trigger a few events on the store. This will cause the state to change and then call our callbacks we registered with the store. View this in your browser. If you bring up the console, you should see the messages we printed to the screen. Notice how the state changes between each one except the last one.

Now that we have that setup, we can start making our application change to reflect our state! See you in the next one.

Chris Sevilleja

161 posts

Co-founder of Scotch.io. Slapping the keyboard until something good happens.