Search code examples
vue.jsvuexvuex-modules

How I can use vuex nested modules?


As Vuex documentation says, when the project start to grow we need a way to separate state management in a more self contained and modularized scalable solution. Here namespaces come in help. Sadly at the moment of writing Vuex modules section lacks of information and examples how to effectively use them.

This is my store structure

store
 |- modules
   |- core
     |- country.js
     |- region.js
     |- ...
   |- roullout
     |- site.js
 |- index.js

Here module register index.js

import Vuex from 'vuex'
// Core modules
import coreCountry from "@/store/modules/core/country";
import coreRegion from "@/store/modules/core/region";
// Rollout modules
import rolloutSite from "@/store/modules/rollout/site";

export default new Vuex.Store({
  modules: {
    // Core modules
    core: {
      country: coreCountry,
      region: coreRegion,
    },
    // Rollout modules
    rollout: {
      site: rolloutSite
    }
  }
})

Now in some component I try to define actions

methods: {
  ...mapActions('rollout/site', [
      LIST_REQUEST
  ])
}

Here the first error: rollout/site is not found because I guess rollout is a folder not a module and therefor site is a module and not a nested module of rollout.

Vuex documentation show how to use helper to bind stuff in nested modules but doesn't show the architecture of a store with nested modules.

LIST_REQUEST is an action, come from types.js and is defined as:

export const LIST_REQUEST = "rollout/site/LIST_REQUEST";

So in site.js module I can use as:

const actions = {
    [types.LIST_REQUEST]: async ({ commit }, payload= {}) => {
        // your actions here...
    },
}

Since I'm using mapActions(), instead of dispatch actions like: this.$store.dispatch() I can directly do this.LIST_REQUEST()

import {
  LIST_REQUEST
} from "@/store/types/rollout/site";

export default {
  name: "SiteList",
  created() {
    this.LIST_REQUEST()
  },
  methods: {
    ...mapActions('rollout/site', [
      LIST_REQUEST
    ])
  }
}

Here another error: LIST_REQUEST() is not a function. This error come because mapAction still have to turn the action into function

How I can fix these problems with nested modules and use index.js. Maybe I have a bit of confusion in my mind, may you help to clarify these concepts?


Solution

  • I was completely misled by the question and the answer given in this post, and it probably also did so to numerous others as it had 1K views at the time of posting this. After figuring it out on my own, I decided to post it here so that it would not waste even more people's time.

    Answer:

    use

    modules: {
      core: {
        namespaced: true,
          modules {
            // Core modules
            country: coreCountry,
            region: coreRegion,
          }
      },
      rollout: {
        namespaced: true,
        modules: {
          // Rollout modules
          site: rolloutSite
        }
      }
    }
    

    (see the explanation below)

    If you don't put in namespaced: true within your coreCountry, coreRegion, and rolloutSite, it will still be like this.$store.dispatch(rollout/LIST_REQUEST). Put in namespaced: true in them if you want this.$store.dispatch(rollout/site/LIST_REQUEST) or, like you said, this.LIST_REQUEST() from using

    methods: {
      ...mapActions('rollout/site', [
        LIST_REQUEST
      ])
    }
    

    For the error with "LIST_REQUEST() is not a function" maybe it's fixed by now, but I thought "rollout/site/LIST_REQUEST" was a weird name for a function. Maybe you can make defining the action work using what you have (maybe by playing around with the syntax around it if it doesn't work?)

    const actions = {
        [types.LIST_REQUEST]: async ({ commit }, payload= {}) => {
            // your actions here...
        },
    }
    

    But, I would instead recommend the more common way (Vuex guide also uses this) to define the action:

    actions: {
      actionName (context, payload) {
        // async functions and commits here
      }
    }
    

    or

    actions: {
      actionName ({ state, rootState, commit, dispatch, getters, rootGetters }, payload) {
        // async functions and commits here
      }
    }
    

    Explanation:

    whatever goes in the modules: { } is considered a module, which by definition (defined in the Vuex API Reference) is an object that is

    key: {
      // required (can be empty)
      state,
    
      // optional (as denoted by '?')
      namespaced?,
      mutations?,
      actions?,
      getters?,
      modules? // **nested modules go here**
    }
    

    So, there are no concept of folders within modules. When you put in

    module {
      core: { ... },
      rollout: { ... }
    }
    

    core and rollout will be recognized as modules not folders.

    So, having

    module {
      rollout: {
        site: { ... }
      }
    }
    

    makes no sense whatsoever. whatever goes inside rollout (recognized as a module) should be either state:, namespaced:, mutations:, actions:, getters:, or modules:. Nothing else.

    So, to put in a module inside modules: { } you can either

    1. directly list imported modules (keep the name from the import statement): modules: { coreCountry, coreRegion }
    2. list imported modules but renamed: modules: { country: coreCountry, region: coreRegion }
    3. or define a module on the spot (without importing): like the core: { module content here } and rollout: { module content here } in the answer I gave, or the account in vuex guide

    Basically, to make nested modules, you just had to put in the sub-modules within the modules: { } of the parent modules (can either be defined on the spot like the answer or be made somewhere else and imported) like core and rollout which are in the modules: { } of the root store themselves.