Build an Interactive JavaScript Food Menu: Lesson 10 of 14

Showing Menu Using the Store

Up Next

Showing Modal Based on Store Flag

Autoplaying in 7 seconds!

Cancel

Now that we have a store to hold all our items, let's put them all in it. There are a couple steps for us to make this happen. First, let's trigger an event on the store to set the items in the store. Remove the reducer and all the triggers and ons from the previous tutorial. We will trigger the event after we receive all the items from our fetch call.

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

We are triggering a SET_ITEMS event on the store and passing it the resBody as items. Now we need to create a reducer that will update the state with these items. We already know how to do this. Put the following above the createStore call.

function reducer(state, event, data) {
  switch (event) {
    case 'SET_ITEMS':
      return Object.assign({}, state, {
        items: data.items.reduce((total, item) =>
          Object.assign({}, total, { [item.id]: item })
          , {}),
      });
    default:
      return state;
  }
}

When the reducer is called, it will find the SET_ITEMS case and run that code. Here were are using Object.assign to move all data from the state into a new object and then we create a new object with items in it that will override what is in the state at that time. The reduce call is used to turn our array of items into a map of items where the ID of the item is the key and the value is the item. The reason we do this will be evident later.

Now that we have the event being triggered, we now need to have something listening for it! Instead of passing the resBody to our application, we need to pass it the store.

body.insertBefore(app(store), body.childNodes[0]);

Next, in app.js, we need to pass that store to the menu module instead of passing the items.

export default function app(store) {
  const modalEle = modal();
  const navbarEle = navbar();
  const heroEle = hero();
  const menuEle = menu(store);
  const bottomEle = bottom();
  const appEle = addId(div(modalEle, navbarEle, heroEle, menuEle, bottomEle), 'app-container');

  return appEle;
}

Now, here's where things get weird. It appears that when building things this way, elements created dynamically cannot be used to change the DOM node once the node has been added to the page. This means we will have to manually grab the node when we want to change it and go from there. That's okay though. It means we get to make more helper functions and explore JavaScript some more! We are going to create a helper that is reminiscent of a library you've probably heard of: jQuery. In the helpers.js file, add the following:

export function $(query) {
  const elements = Array.prototype.slice.call(document.querySelectorAll(query));

  function children(toAdd) {
    elements.forEach(ele => {
      while (ele.firstChild) {
        ele.removeChild(ele.firstChild);
      }

      ele.appendChild(toAdd);
    });
  }

  return {
    children,
  };
}

This helper is $, just like good ole jQuery. It takes in a query and sends that to querySelectorAll. This method will query the DOM and give us a NodeList object of everything that matches. A NodeList is similar to an array, but it doesn't have some of the methods we love, like map and forEach. So we turn it into an array by using Array.prototype.slice. Next we create a children function that loops over each thing in our elements array and removes their children, then appends the node we passed to the function. Lastly, we return an object with this children method attached so we can use it.

Open up the menu.js file and move the creation of each side into a callback that will be registered with the SET_ITEMS event using on. Also let's bring our new helper in and use it to add the menu sides to the menu container.

import { addClass, addId, div, section } from '../builders';
import { $ } from '../helpers';
import leftMenu from './leftMenu';
import rightMenu from './rightMenu';

export default function menu(store) {
  const menuEle = addId(addClass(div(), 'container'), 'menu');

  store.on('SET_ITEMS', ({ items }) => {
    const leftSide = leftMenu(items);
    const rightSide = rightMenu(items);
    const columns = addClass(section(leftSide, rightSide), 'columns');
    $('#menu').children(columns);
  });

  return menuEle;
}

Lastly, since our filterByTpe helper was created to work with an array, let's change this to work with the map that our items are now stored in.

export function filterByType(map, type) {
  return Object.keys(map)
    .filter(key => map[key].type === type)
    .map(key => map[key]);
}

View this in the browser. You should see the menu items still on the page. Nothing has changed except how these items are being added. Pretty cool, huh!

Now that we have demonstrated how we can use the store for some stuff we have already accomplished, let's more onto something new: showing the modal!

Chris Sevilleja

151 posts

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