4

I am building a component that can be used to set various vuex properties, depending on the name passed in the route. Here is the naive gist of it:

<template>
  <div>
    <input v-model="this[$route.params.name]"/>
  </div>
</template>

<script>
export default {
  computed: {
    foo: {
      get(){ return this.$store.state.foo; },
      set(value){ this.$store.commit('updateValue', {name:'foo', value}); }
    },
    bar: {
      get(){ return this.$store.state.bar; },
      set(value){ this.$store.commit('updateValue', {name:'bar', value}); }
    },
  }
}
</script>

Note that I pass this[$route.params.name] to the v-model, to make it dynamic. This works for setting (component loads fine), but when trying to set a value, I get this error:

Cannot set reactive property on undefined, null, or primitive value: null

I assume this is because this inside v-model becomes undefined (?)

How can I make this work?

UPDATE

I would also be curious to know why this does not work (compilation error):

<template>
  <div>
    <input v-model="getComputed()"/>
  </div>
</template>

<script>
export default {
  computed: {
    foo: {
      get(){ return this.$store.state.foo; },
      set(value){ this.$store.commit('updateValue', {name:'foo', value}); }
    },
    bar: {
      get(){ return this.$store.state.bar; },
      set(value){ this.$store.commit('updateValue', {name:'bar', value}); }
    },
  },
  methods: {
    getComputed(){
      return this[this.$route.params.name]
    }
  }
}
</script>
Roy Prins
  • 2,790
  • 2
  • 28
  • 47

1 Answers1

7

Yeah everything inside your <template> is in the this scope, so this is undefined.

v-model is just a syntactic sugar for :value and @input, so you can handle this with a custom event and a computed property for :value.

You can also use a computed property with a getter and setter; Something like

computed: {
  model: {
    get: function () {
      return this.$store.state[this.$route.params.name]
    },
    set: function (value) {
      this.$store.commit('updateValue', { name: this.$route.params.name, value})
    }
  }
}

Edit If you have more logic to do in your setter, i'd separate it like so, keep the getter simple, and stick to one computed property;

computed: {
  model: {
    get: function () {
      return this.$store.state[this.$route.params.name]
    },
    set: function (value) {
      switch(this.$route.params.name) {
        case 'foo':
          return this.foo(value)
        default:
          return this.bar(value)
      }
    }
  }
},
methods: {
  foo(val) {
    this.$store.commit(...)
  },
  bar(val) {
    this.$store.commit(...)
  }
}
Erik Terwan
  • 2,710
  • 19
  • 28
  • I see how the first suggestion would work, but that would complicate code a lot. Regarding the second solution: I do some different checks in the setters, so a generic setter is not really an option. Nothing closer to my naive example possible you think? – Roy Prins Jan 15 '19 at 12:13
  • I was thinking of some intermediate `method` that returns the correct computed property, based on the current route param. Does not seem to work that way though. – Roy Prins Jan 15 '19 at 12:15
  • 1
    Hm, not that i know of. I would divide the setter if you have some more complicated code, split it up that way. Let me update my answer to demonstrate. – Erik Terwan Jan 15 '19 at 12:16
  • Thanks, that doesn't look too bad. I updated the question with an alternative that uses an intermediate method. That gives a compilation error. You know why? – Roy Prins Jan 15 '19 at 12:23
  • 1
    That's because v-model requires a variable that can be set as well as get; a method is always a getter (callable that returns a value/null) but cannot be written to. A computed property w. setter / getter can be written to. – Erik Terwan Jan 15 '19 at 12:28
  • Ah yes, I figured as much but couldn't put it to words. Thanks for the `switch` idea, I think it best conveys the generic intention of the code. – Roy Prins Jan 15 '19 at 12:30