3
votes

I'm trying to add new variables, getters and mutations in the 'created' lifecycle hook in one of my Vuejs components. The variables work fine. But with getters/mutations, it seems it's not as easy as adding new functions to the vuex store object. The problem I'm trying to solve is creating "shared" reusable components.

Here's the component:

<template>
  <div>
    <h2 class="headline">Number of items:</h2>
    <v-select
      :items="limits"
      item-value="value"
      item-text="text"
      label="Select"
      v-model="selected_limit"/>
  </div>
</template>

<script>
  import {selectLimitComponentVariables} from './index';
  import {selectLimitComponentGetters} from './getters';
  import {selectLimitComponentMutations} from './mutations';

  export default {
    created: function () {
      // Add the variables
      Object.assign(this.$store.state[this.ns], selectLimitComponentVariables(this.var_prefix));
      // Add the getters
      Object.assign(this.$store.getters, selectLimitComponentGetters(this.ns, this.var_prefix));
      // Add the mutations
      Object.assign(this.$store._mutations, selectLimitComponentMutations(this.ns, this.var_prefix));
    },
    name: 'SelectLimitComponent',
    props: {
      ns: {
        type: String,
        default: null
      },
      var_prefix: {
        type: String,
        default: ''
      }
    },
    computed: {
      selected_limit: {
        get () {
          return this.$store.state[this.ns].selected_limit;
        },
        set (value) {
          this.$store.commit(`${this.ns}/update${this.var_prefix}selected_limit`, value)
        }
      },
      limits: {
        get() {
          return this.$store.state[this.ns].limits;
        }
      }
    }
  }
</script>

And here are the imported functions. selectLimitComponentVariables:

export const selectLimitComponentVariables = function (prefix) {
  return {
    [`${prefix}selected_limit`]: '100',
    [`${prefix}limits`]: [
      {'text': '10', 'value': '10'},
      {'text': '50', 'value': '50'},
      {'text': '100', 'value': '100'},
      {'text': '250', 'value': '250'},
      {'text': 'No limit', 'value': '-1'}]}
};

selectLimitComponentGetters:

export const selectLimitComponentGetters = function(namespace, prefix){
  return {
    [`${namespace}/${prefix}selected_limit`]: state => state[`${prefix}selected_limit`]
  }
};

selectLimitComponentMutations:

export const selectLimitComponentMutations = function (namespace, prefix){
  return {
    [`${namespace}/update${prefix}selected_limit`]: (state, newLimit) => state[`${prefix}selected_limit`] = newLimit
  }
};

Is there a way to manually add or register new getters/mutations, after the vuex store is created?

3

3 Answers

6
votes

It's better to use store.registerModule feature of vuex to dynamically add new modules to vuex

this.$store.registerModule(`${this.ns}selected_limit`, {
  state: selectLimitComponentVariables...,
  getters: selectLimitComponentGetters...,
  mutations: selectLimitComponentMutations...,
  namespaced: true
})

You can check official document in Dynamic Module Registration section of https://vuex.vuejs.org/en/modules.html

2
votes

It looks like what you're trying to do is have per-component Vuex state.

If you modify the Vuex schema at random like that, it would make it very difficult to manage the separation of state (you're just monkey-patching the root Vuex state).

Here's another approach. You could create a separate Vuex module and register it dynamically in the beforeCreate hook of your component.

Vue.use(Vuex);

const HomeModule = {
  namespaced: true,
  state() {
    return {
      count: 0,
    };
  },
  mutations: {
    increment(state) {
      state.count++;
    },
  },
};

const ns = 'home';

Vue.component('home', {
  template: '<div><button @click="increment">+</button> {{ count }}</div>',
  beforeCreate() {
    if (!this.$store.state[ns]) {
      this.$store.registerModule(ns, HomeModule);
    }
  },
  computed: Vuex.mapState(ns, ['count']),
  methods: Vuex.mapMutations(ns, ['increment']),
});

new Vue({
  el: '#app',
  store: new Vuex.Store(),  // Empty root store
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.3/vuex.js"></script>

<div id="app">
  <home></home>
</div>
  • I've used a ns global variable here, but you could obtain it from a prop like in your example.
  • You can dynamically build the module instead of having it hardcoded.
  • You can unregister the module as well upon component destruction if necessary.
1
votes

The problem with adding values after instantiation is that they don't become reactive without using set

When you are adding a new property that wasn’t present when the data was observed. Due to the limitation of ES5 and to ensure consistent behavior across browsers, Vue.js cannot detect property addition/deletions. The best practice is to always declare properties that need to be reactive upfront. In cases where you absolutely need to add or delete properties at runtime, use the global Vue.set or Vue.delete methods.

try this instead

import Vue from 'vue';

export const selectLimitComponentMutations = function (namespace, prefix){
  return {
    [`${namespace}/update${prefix}selected_limit`]: (state, newLimit) => {
      Vue.$set(state, `${prefix}selected_limit`, newLimit);
    }
  }
};