Vuex Modules

Modularizing Vuex

Vuex Modules

A Vuex module allows you to subdivide the Vuex store such that each module contains it's own state, actions, mutations and getters

Machine

Inventory

Isolated State Systems

Vending Machine State/Inventory Checker

Isolated State Systems

Inventory

Machine State

const store = new Vuex.Store({
  state: {
    inventory: [...]
    machineName: Bender 
    lastServiced: new Date(),
  }
  ...
})

Inventory

Machine

const store = new Vuex.Store({
  state: {
    inventory: [...]
  }
  ...
})
const store = new Vuex.Store({
  state: {
    machineName: Bender 
    lastServiced: new Date(),
  }
  ...
})
const store = new Vuex.Store({
  modules: {
    inventory: {
      state: {
        supply: [...]
      }
    },
    machine: {
      state: {
        machineName: "Bender"
      }
    },
    ...
  }
})
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    inventory: {
      state: {
        supply: [...]
      },
    },
    machine: {
      state: {
        machineName: 'Bender',
        lastStocked: '02/25/2019'
      },
    }
  }
});

store.js

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
export default {
  name: "VendingMachineAdmin",
  computed: {
    stock() {
      return this.$store.state.inventory.supply
    },
    machineName() {
      return this.$store.state.machine.machineName
    },
    lastServiced() {
      return this.$store.state.machine.lastServiced
    }
  },
}
</script>

VendingMachineAdmin.vue

state is modularized

Let's move our store, getters, actions and mutations into separate vuex modules

Exercise Time!

https://github.com/shortdiv/vuex-modules

[step-0]

const store = new Vuex.Store({
  modules: {
    inventory: {
      getters: {
        isSupplyLow(state) {
          return state.supply.filter(
            item => item.supply <= 5
          );
        }
      },
    },
    machine: {
      actions: {
        serviceMachine({ commit }) {
          commit("updateServiceDate", new Date());
      }
    },
  },
})
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    inventory: {
      getters: {
        isSupplyLow(state) {
          return state.supply.filter(item => item.supply <= 5);
        }
      },
    },
    machine: {
      actions: {
        serviceMachine({ commit }) {
          commit("updateServiceDate", new Date());
        }
      },
    }
  }
});

store.js

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
export default {
  name: "VendingMachineAdmin",
  computed: {
    isSupplyLow() {
      return this.$store.getters.isSupplyLow
    }
  },
  methods: {
    serviceMachine() {
      return this.$store.dispatch("serviceMachine")
    },
  },
}
}
</script>

VendingMachineAdmin.vue

actions, getters, mutations are global

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    inventory: {
      getters: {
        isSupplyLow(state) {
          return state.supply.filter(item => item.supply <= 5);
        }
      },
    },
    machine: {
      actions: {
        serviceMachine({ commit }) {
          commit("updateServiceDate", new Date());
        }
      },
    }
  }
});

store.js

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
import { mapActions, mapGetters } from "vuex";
export default {
  name: "VendingMachineAdmin",
  computed: {
    ...mapGetters(["isSupplyLow"]);
  },
  methods: {
    ...mapActions(["serviceMachine"]);
  },
}
}
</script>

VendingMachineAdmin.vue

actions, getters, mutations are global

const store = new Vuex.Store({
  modules: {
    inventory: {
      namespaced: true,
      state: { 
        stock: [...]
      }
    },
    machine: {
      namespaced: true, 
      state: {
        machineName: 'Bender’,
        lastServiced: '01/25/2020'
      }
    }
  }
});

Inventory

Machine

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
import { mapActions, mapActions } from "vuex";
export default {
  name: "VendingMachineAdmin",
  computed: {
    stock() {
      return this.$store.state.inventory.stock
    },
    lastServiced() {
      return this.$store.state.machine.lastServiced
    }
  },
  methods: {
    restock() {
      this.$store.dispatch("inventory/fetchFromInventory");
    },
    serviceMachine() {
      this.$store.dispatch("machine/serviceMachine")
    }
  }
 }
 </script>

VendingMachineAdmin.vue

Let's namespace our vuex module and update the actions, and getters in the component appropriately

Exercise Time!

https://github.com/shortdiv/vuex-modules

[step-1]

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
import { mapActions, mapActions } from "vuex";
export default {
  name: "VendingMachineAdmin",
  computed: {
    ...mapState({
      stock: state => state.inventory.stock,
      machineName: state => state.machine.machineName,
      lastServices: state => state.machine.lastServiced
    })
  },
  methods: {
    ...mapActions([
      'inventory/fetchFromInventory',
      'machine/serviceMachine'
    ])
   }
 }
 </script>

VendingMachineAdmin.vue

...mapState({
  a: state => state.module.name.a,
  b: state => state.module.name.b
})

mapState

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    inventory: {
      state: {
        supply: [...]
      },
    },
    machine: {
      state: {
        machineName: 'Bender',
        lastStocked: '02/25/2019'
      },
    }
  }
});

store.js

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "VendingMachineAdmin",
  computed: {
    ...mapState({
     stock: state => state.inventory.supply,
     machineName: state => state.machine.machineName,
     lastServiced: state => state.machine.lastServiced
    })
  },
}
</script>

VendingMachineAdmin.vue

state is modularized

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
import { mapActions } from "vuex";
export default {
  name: "VendingMachineAdmin",
  computed: {
    ...mapState({
      stock: state => state.inventory.stock,
      machineName: state => state.machine.machineName,
      lastServices: state => state.machine.lastServiced
    })
  },
  methods: {
    ...mapActions([
      'inventory/fetchFromInventory',
      'machine/serviceMachine'
    ])
   }
 }
 </script>

VendingMachineAdmin.vue

...mapActions([
  'moduleName/foo',
  'moduleName/bar'
])

mapActions

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    inventory: {
      namespaced: true,
      state: {
        stock: [...]
      },
      actions: {
        fetchFromInventory() {...}
      }
    },
    machine: {
      namespaced: true,
      state: {
        machineName: 'Bender',
        lastStocked: '02/25/2019'
      },
      actions: {
        serviceMachine() {...}
      }
    }
  }
});

store.js

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
import { mapState, mapActions } from "vuex";
export default {
  name: "VendingMachineAdmin",
  computed: {
   ...mapState({
     stock: state => state.inventory.stock,
     machineName: state => state.machine.machineName,
     lastServiced: state => state.machine.lastServiced
   })
  },
  methods: {
    ...mapActions([
      'inventory/fetchFromInventory',
      'machine/serviceMachine'
    ])
   }
 }
 }
 </script>

VendingMachineAdmin.vue

<template>
  <div>
    ...
    {{ lastServiced }}
    <button @click="serviceMachine">
      Service
    </button>
  </div>
</template>

<script>
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('inventory')

export default {
  name: "VendingMachineAdmin",
  computed: {
    ...mapState({
      stock: state => state.stock, // state.inventory.stock 
      
    })
  },
  methods: {
    ...mapActions([
      'fetchFromInventory' // 'inventory/fetchFromInventory'
    ])
  }  
}
</script>

VendingMachineAdmin.vue

├── src/
  ├── components
  ├── App.vue
    ├── store/
      ├── module/
        └── inventory.js    
    └── index.js
  └── main.js
├── ...
└── public/
module/
inventory.js

Vuex Modules Folder Structure

index.js
const module = {
  namespaced: true,
  state: { ... },
  actions: { ... },
  getters: { ... },
  mutations: { ... }
};

export default module;

store/modules/inventory.js

import Vue from "vue";
import Vuex from "vuex";
import inventory from "./modules/inventory";

Vue.use(Vuex);

const modules = { inventory };

const store = new Vuex.Store({
  modules
});

export default store;

store/index.js

├── src/
  ├── components
  ├── store/
    ├── module/
      ├── inventory
        ├── state.js
        ├── ...
        └── index.js
    └── index.js
└── public/
module/
inventory/

Vuex Modules Folder Structure

state.js
index.js

Static vs Dynamic Modules

Vending Machine Service

const store = new Vuex.Store({
  modules: {
    machine: {
      namespaced: true, 
      state: {
        machineName: 'Bender’,
        timesServiced: 0
      },
      ...
    }
  }
});

registered on init

├── src/

       ├── state/

          ├── modules/             

              ├──  machine.js

         ├── store.js

     ├── ...

     └── App.vue

├── ...

└── public/

state/

Vuex Folder Structure

modules/

machine.js

store.js

import Vue from "vue";
import Vuex from "vuex";
import machine from "./modules/machine";

Vue.use(Vuex);

const modules = {
  machine
};

const store = new Vuex.Store({
  modules
});

export default store;

store.js

export default {
  namespaced: true,
  state() {
    return {
      timesServiced: 0
    };
  },
  actions: {
    serviceMachine({ commit }) {
      commit("completeServiceRequest");
    }
  },
  getters: {},
  mutations: {
    completeServiceRequest(state) {
      state.timesServiced++;
    }
  }
};

./state/modules/machine.js

import Vue from "vue";
import Vuex from "vuex";
import machine from "./modules/machine";

Vue.use(Vuex);

const modules = {
  machine
};

const store = new Vuex.Store({
  modules
});

export default store;

store.js

export default {
  namespaced: true,
  state() {
    return {
      timesServiced: 0
    };
  },
  actions: {
    serviceMachine({ commit }) {
      commit("completeServiceRequest");
    }
  },
  getters: {},
  mutations: {
    completeServiceRequest(state) {
      state.timesServiced++;
    }
  }
};

./state/modules/machine.js

export default {
  ...
  created() {
    this.registerStoreModule("machine", machineModule);
  },

  methods: {
    registerStoreModule(moduleName, storeModule) {
      const store = this.$store;

      if (!(store && store.state &&
            store.state[moduleName])) {
        store.registerModule(moduleName, storeModule);
      }
    }
  }
};

register module in component instance

export default {
  ...
  computed: {
    ...mapState("machine", ["timesServiced"])
  },
  created() {
    this.registerStoreModule("machine", machineModule);
  },
  methods: {
    ...mapActions("machine", ["serviceMachine"]),
    registerStoreModule(moduleName, storeModule) {
  ...
}
 
} };

use module as named

https://github.com/shortdiv/vuex-static-vs-dynamic-modules

[step-0]

Exercise Time!

In the `operatorView` component, extrapolate the store and dynamically load it so that servicing the primary machine doesn't update the other machines

(this step may cause a console error, but ignore that for now)

https://github.com/shortdiv/vuex-static-vs-dynamic-modules

[step-1]

Exercise Time!

Similarly, in the `vendingMachineItem` component, extrapolate the store and dynamically load it

import machineModule from "../store/modules/machine";

export default {
  name: "VendingMachineItem",
  props: {
    machine: Object
  },
  data() {
    return {
      machineId: null
    };
  },
  computed: {
    ...mapState({
      timesServiced: state => state.machine.timesServiced
    })
  },
  methods: {
    ...mapActions("machine", ["serviceMachine"]),
    registerStoreModule(moduleName, storeModule) {
      const store = this.$store;
      if (!(store && store.state && store.state[moduleName])) {
        store.registerModule(moduleName, storeModule);
      }
    }
  },
  created() {
    this.registerStoreModule("machine", machineModule);
  }
};

module name is duplicated

https://github.com/shortdiv/vuex-static-vs-dynamic-modules

[step-2]

Exercise Time!

In the `vendingMachineItem` component, create a unique id for every module, this way, the module for every item isn't shared

import machineModule from "../store/modules/machine";

export default {
  name: "VendingMachineItem",
  props: {
    machine: Object
  },
  data() {
    return {
      machineId: null
    };
  },
  computed: {
    timesServiced() {
      return this.$store.state[this.machineId].timesServiced;
    }
  },
  methods: {
    serviceMachine() {
      this.$store.dispatch(`${this.machineId}/serviceMachine`);
    },
    registerStoreModule(moduleName, storeModule) {
      const store = this.$store;
      if (!(store && store.state && store.state[moduleName])) {
        store.registerModule(moduleName, storeModule);
      }
    }
  },
  created() {
    this.machineId = this.machine.name.replace(" ", "").toLowerCase();
    this.registerStoreModule(`${this.machineId}`, machineModule);
  }
};
import machineModule from "../store/modules/machine";

export default {
  name: "VendingMachineItem",
  props: {
    machine: Object
  },
  data() {
    return {
      machineId: null
    };
  },
  computed: {
    timesServiced() {
      return this.$store.state[this.machineId].timesServiced;
    }
  },
  methods: {
    serviceMachine() {
      this.$store.dispatch(`${this.machineId}/serviceMachine`);
    },
    registerStoreModule(moduleName, storeModule) {
      const store = this.$store;
      if (!(store && store.state && store.state[moduleName])) {
        store.registerModule(moduleName, storeModule);
      }
    }
  },
  created() {
    this.machineId = this.machine.name.replace(" ", "").toLowerCase();
    this.registerStoreModule(`${this.machineId}`, machineModule);
  },
  beforeDestroy() {
    this.$store.unregisterModule(`${this.machineId}`);
  }
};

remember to unregister module on component teardown

Vuex

By shortdiv

Vuex

  • 761