Search code examples
vue.jsvuexvue-routervuex-modules

Same VueX store module registered from components on two pages


I have encountered a weird case when using VueX and Vue-Router and I am not too sure how to cleanly solve it.

I have a component (let's call it "ComponentWithStore") that registers a named store module a bit like this : (the actual content of the store don't matter. obviously in this toy example using VueX is overkill, but this is a very simplified version of a much more complexe app where using VueX makes sense)

// ComponentWithStore.vue
<script>
import module from './componentStore.js';

export default {
    name: 'ComponentWithStore',
    beforeCreate() {
        this.$store.registerModule(module.name, module);
    },

    beforeDestroy() {
        this.$store.unregisterModule(module.name);
    }
}
</script>

Then I place this component in a view (or page) which is then associated to a route (let's call this page "Home").

// Home.vue
<template>
  <div class="home">
    Home
    <ComponentWithStore/>
  </div>
</template>

<script>
import ComponentWithStore from '@/components/ComponentWithStore.vue';

export default {
  name: "Home",
  components: { ComponentWithStore }
};
</script>

So far so good, when I visit the Home route, the store module is registered, and when I leave the Home route the store module is cleaned up.

Let's say I then create a new view (page), let's call it "About", and this new About page is basically identical to Home.vue, in that it also uses ComponentWithStore.

// About.vue
<template>
  <div class="about">
    About
    <ComponentWithStore/>
  </div>
</template>

<script>
import ComponentWithStore from '@/components/ComponentWithStore.vue';

export default {
  name: "About",
  components: { ComponentWithStore }
};
</script>

Now I encounter the following error when navigating from Home to About :

vuex.esm.js?2f62:709 [vuex] duplicate namespace myComponentStore/ for the namespaced module myComponentStore

What happens is that the store module for "About" is registered before the store module for "Home" is unregistered, hence the duplicate namespace error.

So I understand well what the issue is, however I am unsure what would be the cleanest solution to solve this situation. All ideas are welcome

A full sample may be found here : https://github.com/mmgagnon/vue-module-router-clash To use, simply run it and switch between the Home and About pages.


Solution

  • As you have mentioned, the issue is due to the ordering of the hooks. You just need to use the correct hooks to ensure that the old component unregisters the module first before the new component registers it again.

    At a high level, here is the order of hooks in your situation when navigating from Home to About:

    1. About beforeCreate
    2. About created
    3. Home beforeDestroy
    4. Home destroyed
    5. About mounted

    So you can register the module in the mounted hook and unregister it in either beforeDestroy or destroyed.

    I haven't tested this though. It might not work if your component requires access to the store after it is created and before it is mounted.


    A better approach is to create an abstraction to register and unregister modules that allows for overlaps.

    Untested, but something like this might work:

    function RegistrationPlugin(store) {
      const modules = new Map()
    
      store.registerModuleSafely = function (name, module) {
        const count = modules.get(name) || 0
    
        if (count === 0) {
          store.registerModule(name, module)
        }
    
        modules.set(name, count + 1)
      }
    
      store.unregisterModuleSafely = function (name) {
        const count = modules.get(name) || 0
    
        if (count === 1) {
          store.unregisterModule(name)
          modules.delete(name)
        } else if (count > 1) {
          modules.set(name, count - 1)
        }
      }
    }
    

    Specify the plugin when you create your store:

    const store = new Vuex.Store({
      plugins: [RegistrationPlugin]
    })
    

    Now register and unregister your modules like this:

    beforeCreate() {
      this.$store.registerModuleSafely(module.name, module)
    },
    
    destroyed() {
      this.$store.unregisterModuleSafely(module.name)
    }