2

I'm trying to bind user input from a form to a state in my vuex store.

The state looks like this:

customers: [
  {firstName: "", lastName: "", age: ""},
  {firstName: "", lastName: "", age: ""},
  {firstName: "", lastName: "", age: ""}
]

I've tried to use v-model on a computed property that invokes get and set method. I found an explanation here.

This works perfectly for an object, but unfortunately there is no explanation how to use this on an array of objects.

I'm looking for something like this:

computed: {
  firstName: {
    get () {
      return this.$store.state.customers[i].firstName
    },
    set (value) {
      this.$store.commit('changeFirstname', {value, index})
    }
  }
}

But obviously this didn't work, because I can't pass the index to the computed property. Has anybody a solution for this? Is this a good use case for a deep watcher?

This is my first question, please let me know if I forget something or done something wrong, so that I can improve my asking. Thanks!

Matthias
  • 1,759
  • 10
  • 19
  • Possible duplicate of [Can I pass parameters in computed properties in Vue.Js](https://stackoverflow.com/questions/40522634/can-i-pass-parameters-in-computed-properties-in-vue-js) – The Reason Jul 01 '19 at 20:14

1 Answers1

1

Vuex can be a bit of a pain when your data isn't flat. See https://forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143 for more on that.

One way you could approach this is to ditch v-model and use :value and @input directly. I've simulated a store in my example but hopefully it's clear what's going on. You can easily grab the index of the customer if you'd find that easier than passing around the object itself.

const storeCustomers = Vue.observable([
  {firstName: "A", lastName: "", age: ""},
  {firstName: "B", lastName: "", age: ""},
  {firstName: "C", lastName: "", age: ""}
])

function storeGetCustomers () {
  return storeCustomers
}

function storeUpdateCustomerFirstName (customer, value) {
  customer.firstName = value
}

new Vue({
  el: '#app',
  
  computed: {
    customers () {
      return storeGetCustomers()
    }
  },
  
  methods: {
    onFirstNameInput (customer, value) {
      storeUpdateCustomerFirstName(customer, value)
    }
  }
})
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<div id="app">
  <div v-for="customer in customers">
    <input :value="customer.firstName" @input="onFirstNameInput(customer, $event.target.value)">
  </div>
  <p>
    {{ customers }}
  </p>
</div>

An alternative that retains the v-model would be to introduce a separate component to hold each customer. In general when using v-for for anything involving editing it's worth considering introducing a new component.

In this example there's an asymmetry in that the customers are read from the store in the parent and written to the store in the child. I find that a little uncomfortable. Perhaps if each customer had an id then that id could be passed to the child as a prop instead of the customer object, leaving the child to get and set the individual customer it's interested in.

const storeCustomers = Vue.observable([
  {firstName: "A", lastName: "", age: ""},
  {firstName: "B", lastName: "", age: ""},
  {firstName: "C", lastName: "", age: ""}
])

function storeGetCustomers () {
  return storeCustomers
}

function storeUpdateCustomerFirstName (customer, value) {
  customer.firstName = value
}

const child = {
  props: ['customer'],
  
  template: `
    <div>
      <input v-model="firstName">
    </div>
  `,
  
  computed: {
    firstName: {
      get () {
        return this.customer.firstName
      },
      
      set (value) {
        storeUpdateCustomerFirstName(this.customer, value)
      }
    }
  }
}

new Vue({
  el: '#app',
  
  components: {
    child
  },
  
  computed: {
    customers () {
      return storeGetCustomers()
    }
  }
})
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<div id="app">
  <child v-for="customer in customers" :customer="customer"></child>
  <p>
    {{ customers }}
  </p>
</div>

To your question about deep watchers, I believe what you're describing would involve mutating the state outside the store, then having a watcher let the store know about it. There wouldn't actually be anything for the store to do as the state would already have been changed. Obviously this would violate 'the rules' about only mutating store state inside the store's mutations.

skirtle
  • 22,128
  • 2
  • 23
  • 49
  • Thanks for this great explanation @skirtle! I appreciate your time and effort. I've followed your second suggestion and it works perfectly. – Matthias Jul 02 '19 at 09:04