Home > Back-end >  Vuex, update property of object in array causes many mutation methods
Vuex, update property of object in array causes many mutation methods

Time:11-07

Let's say you have a store like this. And a component where you select a car from the list which gets passed as a prop to another component where you can edit various properties of the car object.

If the car objects had more properties like brand, weight etc. I would have to either:

  • Create a new mutation for every property This has the downside of causing a huge amount of mutations, which will make the store file very unreadable in my opinion.

  • Create a generic mutation that takes an object, a field, and a value and assign that. This has the benefit of only having one generic mutation. But it has the downside that it can't handle nested objects.

  • Create a new car object in the component and replace that in the state by id field. This feels like a clean solution but it would involve copying the object to remove mutability, and that feels less clean. And also slow down the app if use a lot I guess.

In my projects, I've ended up with a mix of these options which just feels chaotic.

So, my question is if there's a better pattern to use than any of these? And if not, which is considered the best practice. I've searched for an answer to this for a long time but never found any good explanation.

state: {
    cars: [
        {id: 1, color: "blue"},
        {id: 2, color: "blue"},
        {id: 3, color: "blue"},
    ]
},
mutations: {
    setColor(state, {car, color}){
        car.color = color;
    }
},
getters: {
    allCars(state){
        return state.cars;
    }
},

CodePudding user response:

I will share my views on this subject. Sincerely I don't know the best practice, and there are a handful of variations of the same approach.

Basically, it applies only the second principle you mentioned, and keeps the reactivity in place for the whole update cycle.

In the store make a generic mutation:

mutations: {
    setItem(state, item) {
      let idx = state.items.findIndex((i) => i.id === item.id);
      Vue.set(state.items, idx, item);
    }
}

In component map the state items to a computed property, and make another one for the edited item:

computed: {
    ...mapGetters({ allItems: "allItems" }),
    localItem: {
      get() {
        return this.allItems[0];
      },
      set(val) {
        this.setItem(val);
      },
    },
},
methods: {
    ...mapMutations(["setItem"])
}

And the tricky part, update the local item:

methods: {
    ...mapMutations(["setItem"]),
    changeItem() {
      this.$set(this, "localItem", { ...this.localItem, color: "red" });
    },
}

Do not update directly object properties but rather assign the whole object, so that the computed setter is triggered and changes are committed to store, without any warnings.

Codesandbox

CodePudding user response:

I would do it using deepMerge function:

export const mutations = {
  setCarProperties(state, car) {
    const index = state.cars.findIndex((item) => item.id === car.id);

    Vue.set(state.cars, index, deepMerge(state.cars[index], car));
  }
};

So you can change and add properties (even nested):

updateCarType() {
  const car = {
    id: 2,
    type: {
      engine: "electric",
    },
  };
  this.$store.commit("setCarProperties", car);
},

Please note I am using Vue.set in mutation. You may wonder why I don't change state like usually we do:

state.cars[index] = deepMerge(state.cars[index], car));

But that won't be reactive, so Vue.set is needed. https://vuejs.org/v2/api/#Vue-set

Check demo: https://codesandbox.io/s/questions-69864025-vuex-update-property-of-object-in-array-causes-many-mutation-methods-sq09z?file=/pages/index.vue

CodePudding user response:

You can pass whole car object, with changed properties, to action:

async carUpdate({ commit }, car) {
  await // some axios or fetch call...
  commit('updateCar', car);
},

then commit mutation like:

updateCar(state, car) {
  state.cars = [
    ...state.cars.map((item) =>
      item.id !== car.id
        ? item
        : {
            ...item,
            ...car,
          }
    ),
  ];
},
  • Related