Vuex form best practices


#1

As far as I understand there are two suggested ways of dealing with complex form data with Vuex but I’m having issues with each and I’d love some direction.

1. :value and @change

Based on topics like this I’ve been led to believe that the optimal way to handle forms with Vuex data is to set the computed property with :value and then commit to the store on @change. This approach works but results in vue methods like

update: function (key, value, subkey) {
  let update = {}
  if (subkey !== undefined){
    update = {
      [subkey]: {
        [key]: value
      }
    }
  } else {
    update = {
      [key]: value
    }
  }
  this.$store.commit('UPDATE_VALUE', update)
}

and $store.mutations like

UPDATE_VALUE (state, value) {
  _.merge(state.person, value)
}

this strikes me as overly verbose and kind of brittle but I’ve not found a more logical way to work with complicated objects. Am I missing something?

Working pen here




2. Initialize and save

The other suggestion I’ve seen is to work with the data in a local model and then submit it to Vuex on save if the UI allows.

I’ve set the store data to local like this

 data () {
    return {
       person: _.cloneDeep( this.$store.state.person)
    }
 }

which works until I save with this mutation

UPDATE_DATA (state, payload) {
  state.person = payload
}

at which point every additional update throws the illegal mutation error:

[vuex] Do not mutate vuex store state outside mutation handlers.

I’m sure this has something to do with the way I’m initially cloning the data but I can’t figure it out.

Working pen of this here

Any suggestions on either would be appreciated or - if I’m missing a better way to handle form data - I’d love to learn


#2

The two approaches really do different things, so I can’t say which one is “better”:

  • The first one “saves” all changes to the store immediatly, so you could send all changes to the server immediatly as well - all changes apply instantly.
  • The second approach is “caching” changes locally until the user actually confirms the save.

So it depends which functionality you want for your users.

You initially clone the object so you don’t directly mutate the state, right? But then you save it, and send this new, cloned object to the store and save it there. But it also stays in the component, so any subsequent edits will mutate this object locally in your component, and therefore, also in the store.

So what you would have to do is either watch the store for changes and then clone again locally:

data () {
    return {
       person: _.cloneDeep( this.$store.state.person)
    }
 },
computed: {
  storePerson() {
    return _.cloneDeep(this.$store.stat.person)
  }
},
watch: {
  storePerson(newValue) {
    this.person = newValue
  }
}

or write a mnutation that only updates the values of the object instead of replacing the whole object:

UPDATE_DATA (state, payload) {
  Object.key(payload).forEach(key => {
    state.person[key] = payload[key]
  })
}

The latter approach is a bit brittle though, because as soon as one of the person.properties contains another object or array, you would have to clone that as well etc.

If you feel that the first approach is a bit too much boilerplate, you can extract this logic into a mixin pretty nicely.


#3

Thanks! Embarrassed I missed the fact that I wasn’t cloning the object again but that’s 100% the problem.

I’m realizing my core issue is that my store has a bunch of nested objects and working with them is a pain because it requires a ton of code (as you allude to.) I can’t really change the data structure as I’m using a framework that requires that structure - is there a better way of dealing with this or is that just the cost of the good stuff about Vuex?


#4

Yes and no. It’s generally better to normalize nested structures into flat ones, so that for each parent object, the nested object is only represented by an id (which requires every object has an id), and then you dynamically look up nested objects as you need them.

That usually makes working with those objects in vuex much, much easier.

A good tool to normalize nested data structures is normalizr, the de facto standard tool in the redux community afaict.

This tool can also reconstruct nested structures from flat ones, so maybe you can use that to provide the nested structure that your framework requires, whileworking with normalized data in your store?

There have been a couple of discussions about this on the forum already, here are two:

…or simply search for “normalizr”, it’s usually mentioned in ever thread about this topic :smiley:

Maybe this kind of data is not a good fit for vuex. Not everything has to be forced into the store. But that’s hard to judge for me, I know nothing about your situation.


#5

Oh wow! This rules and I had no idea it was even an option (this is my first time using a store so this totally passed me by.) Thanks I’ll read up on those threads but it seems to be exactly what I need. Appreciate the help :slight_smile: