Skip to content

Instantly share code, notes, and snippets.

@jonboiser
Created July 26, 2018 00:16
Show Gist options
  • Save jonboiser/b12e4bd3fb33666e33e14b252e89536b to your computer and use it in GitHub Desktop.
Save jonboiser/b12e4bd3fb33666e33e14b252e89536b to your computer and use it in GitHub Desktop.
vuex modules

Organizing Page States in Vuex Modules

Summary

Prior to this pull request, the pattern for rendering pages was as follows:

  1. When browser goes to a route, the router executes a handler function (with a name like showClassesPage)
  2. The handler will have some standard effects on the Vuex store (e.g. show loading page, update a pageName field that matches the route's name, etc.). It will usually request some data from the server and then terminate by committing a SET_PAGE_STATE mutation that copies that server data into Vuex inside of the pageState field.
  3. The Vue component that is mapped to pageName will render and pull the data it needs to present from pageState.

After this Pull Request, this flow changes in the following manner:

  1. For each observed shape of pageState in Kolibri, a new namespaced Vuex module was created. In the module, the default shape of the data is defined. Any actions, mutations, and getters that are associated with that version of pageState were also added to that module. The names of these modules was generally derived from the pageName associated with that data. For example, the manageContent module was so-named since it governs the MANAGE_CONTENT_PAGE.
  2. A handler completes its execution by using a mutation that updates the state of a specific Vuex module. For example, at the end of showDeviceInfoPage, the mutation is deviceInfo/SET_STATE. Only the deviceInfo module is affected, hence the prefixing of deviceInfo/ to that mutation name.
  3. Any Vue components that originally drew from the generic pageState now draw from the module created from that pageState. Similarly, any references to actions, getters, or mutations that component used were updated to specify the module namespace.
  4. When leaving a route, there is a "cleanup" step where a mutation module/RESET_STATE will remove any data that was inserted when we entered the route.

This architectural change was accompanied by a major change in how the source code is organized.

  1. In each plugin, the /state folder is replaced with a /modules folder. The entry point for /modules is /modules/pluginModule.js.
  2. For each module, there is a subfolder with some or all of the following modules: 3. index.js: Exports an object defining a Vuex module. Usually, the state and mutations properties are defined inside the object, whereas actions is imported, as actions tend to be more complex.
    1. handlers.js: Contains handler functions that are used by the router to orchestrate the data-fetching and other side-effects needed to show a particular page. Originally these were grouped with 'actions', but are distinct since they are never called from within a Vue component--only by the router.
    2. actions.js: Contains actions that fetch data and usually terminate in updating the module's state with the results of that data.
  3. In a small number of cases, this organization may be different if e.g., getters or mutations were already defined in a file and could simply be moved to a new location.

How to check the code

Here are some formal checks that one should do:

Check core/top-level actions and mutations inside of namespaced actions

If a high-level action/mutation is used inside of a namespaced action, it must include a {root:true} option. For example:

Bad:

// Inside a namespaced action
function updateUser(store, updateData) {
  store.commit('CORE_SET_PAGE_LOADING', true);
  // ... do other stuff
}

Good:

// Inside a namespaced action
function updateUser(store, updateData) {
  store.commit('CORE_SET_PAGE_LOADING', true, { root: true });
  // ... do other stuff
}

However, in handlers specifying {root:true} is not necessary, since those functions are injected with a store object that interacts with the global store.

If a namespaced action is referencing a mutation or action defined within the same module, you do not need to provide a module prefix or use {root:true}.

Check actions and mutations inside of handlers

As explained above, inside a handler, one does not need to make changes when referencing a global action or mutation. However if one is referencing a namespaced action/mutation, one needs to provide the appropriate prefix:

Bad:

// Inside a handler
function showProfilePage(store, userId) {
  return getProfileData(userId).then(data => {
    store.commit('SET_STATE', data);
  })
}

Good:

// Inside a handler
function showProfilePage(store, userId) {
  return getProfileData(userId).then(data => {
    store.commit('profilePage/SET_STATE', data);
  })
}

Check vuex bindings within components

Components should only operate with one module (and possibly with some data defined in core and the top-level). So when we bind that component to Vuex, we need to make sure it is being bound to the right place.

Check that each call of mapState, mapGetters, mapMutations, and mapActions inside of a component includes the correct prefix, if necessary.

Bad:

computed: {
  ...mapState(['facilityUsers']),
  ...mapState('module', ['notReallyInModule']),
},
methods: {
   ...mapActions(['deleteUser']),
},

Good:

computed: {
  ...mapState('users', ['facilityUsers']),
  ...mapState('module', ['actuallyInModule']),
},
methods: {
   ...mapActions('users', ['deleteUser']),
},

Check references to state and getters within namespaced actions and getters

Occasionally a namespaced action or getter will need to refer to data outside of its module. The store object injected to these functions provides properties rootState and rootGetters in order to access these data.

Bad:

function getLotsOfStuff(state, getters) {
	return {
	  foo: state.foo,
	  coreFoo: state.core.foo,
	  coreGetterFoo: getters.foo,
	}
}

function actOnStuff(store) {
  const { foo } = store.state;
  const { coreFoo } = store.state.core;
  // do other stuff
}

Good:

function getLotsOfStuff(state, getters, rootState, rootGetters) {
	return {
	  foo: state.foo,
	  coreFoo: rootState.core.foo,
	  coreGetterFoo: rootGetters.foo,
	}
}

function actOnStuff(store) {
  const { foo } = store.state;
  const { coreFoo } = store.rootState.core;
  // do other stuff
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment