Search code examples
vue.jsvuejs2vuex

How to make vuex DRY when defining global accessed property?


I've some generic state like error and session in $store which I want to be accessed as computed property error and session in all components (including those in router). I know I should do like this:

var store = new Vuex.Store({
  state: {
    error: undefined,
    session: JSON.parse(localStorage.getItem('session')),
  },
  mutations: {
    error: function(state, error) {
      state.error = error
    },
    session: function(state, session) {
      state.session = session
    },
  },
})

var loginView = new Vue({
  computed: {
    error: {
      get () {
        return this.$store.state.error
      },
      set (val) {
        this.$store.commit('error', val)
      }
    },
    session: {
      get () {
        return this.$store.state.session
      },
      set (val) {
        this.$store.commit('session', val)
      }
    },
  },
})

var router = new VueRouter({
  routes: [
    {
      path:'/login', 
      component: loginView,
    },
  ],
})

var app = new Vue({
  store,
  router,
  computed: {
    error: {
      get () {
        return this.$store.state.error
      },
      set (val) {
        this.$store.commit('error', val)
      }
    },
    session: {
      get () {
        return this.$store.state.session
      },
      set (val) {
        this.$store.commit('session', val)
      }
    },
  },
})

As you can see, it's not DRY. What if I have 100 components in router and I want them to be able to have computed properties like this? Is there something to solve this? Maybe something like an option globals in Vuex:

var store = new Vuex.Store({
  globals: ['error', 'session'], // this will enable all components' computed property of error and session
  state: {
    error: undefined,
    session: JSON.parse(localStorage.getItem('session')),
  },
  mutations: {
    error: function(state, error) {
      state.error = error
    },
    session: function(state, session) {
      state.session = session
    },
  },
})

var loginView = new Vue({
  computed: {
  },
})

var router = new VueRouter({
  routes: [
    {
      path:'/login', 
      component: loginView,
    },
  ],
})

var app = new Vue({
  store,
  router,
  computed: {
  },
})

Solution

  • I do not see anywhere in the documentation that Vuex supports the set of a computed value. If it did, you would probably be better off using mapGetters.

    If you want to follow the spirit of Vuex, I expect the best approach would be to take advantage of mapGetters and mapActions/mapMutations and map your set to an action/mutation.

    You could also use a mixin to do this. I know you said you want to define these values globally (and you could do that with a global mixin), but you could stay DRY by defining a mixin with these properties and apply it where you actually need them.

    Lastly you could use a global mixin. Take note of the caveats of using a global mixin in the linked documentation.

    Vue.mixin({
      computed: {
        error: {
          get () {
            return this.$store.state.error
          },
          set (val) {
            this.$store.commit('error', val)
          }
        },
        session: {
          get () {
            return this.$store.state.session
          },
          set (val) {
            this.$store.commit('session', val)
          }
        },
      }
    })
    

    error is a pretty generic name and you may want to rethink it if this is meant to be a property of every component.