vuejs / vuex

🗃️ Centralized State Management for Vue.js.
https://vuex.vuejs.org
MIT License
28.42k stars 9.58k forks source link

[Vuex] Store.commit() persists all pending changes on object lists, even if method does nothing #2203

Closed enloc-port closed 1 year ago

enloc-port commented 1 year ago

Version

Vue: 2.6.14 Vuex: 3.5.1

Description

We did not find any documentation of the behavior we encountered with the store's commit() method when working with a list of objects, so we do not know what to make of it.

It seems like any call to commit() persists all pending changes to the state, even if the method called in commit() does not do anything.

In the example below, when editing one of the fields and clicking "Commit nothing", the changes are persisted in the state, even though the store's doNothing() method did not do anything.

The Vuex "Mutations" page does not address this at all, hence we wonder, is this a bug or intended behavior? In case of the latter, it should be documented and emphasized on the "Mutations" page, because it is very unintuitive.

Example

example.vue

<template>
  <div>
    <div>
      <input v-model="computedObject.string"/>
      <input type="number" v-model="computedObject.number"/>
    </div>

    <div>
      <div>computedObject.string: {{ computedObject.string }}</div>
      <div>computedObject.number: {{ computedObject.number }}</div>
    </div>

    <button @click="clearList">Clear list</button>
    <button @click="resetList">Reset list</button>
    <button @click="commitNothing">Commit nothing</button>
  </div>
</template>

<script>
export default {
  name: "example",
  methods: {
    clearList() {
      this.$store.commit("clearList");
    },
    resetList() {
      this.$store.commit("resetList");
    },
    commitNothing() {
      this.$store.commit("doNothing");
    },
  },
  computed: {
    computedObject() {
      return this.$store.state.someList.at(-1) ?? {};
    },
  },
}
</script>

store.js

import Vue from "Modules/vue";
import Vuex from "Modules/vuex";

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        someList: [{
            string: "Hello World!",
            number: 123,
        }],
    },

    mutations: {
        clearList(state) {
            state.someList = [];
        },

        resetList(state) {
            state.someList = [{
                string: "Hello World!",
                number: 123,
            }];
        },

        doNothing() {
            console.log("I did nothing.");
        },
    },
});
enloc-port commented 1 year ago

Can anyone at least confirm that this is a thing and maybe say whether it's a bug or a feature?

enloc-port commented 1 year ago

@yyx990803 @kiaking If you cannot say anything about this, would you mind telling me who to contact in order to get a reaction to this issue?

cuebit commented 1 year ago

This is neither a feature or a bug.

You're mutating the state directly. You can verify this claim by enabling strict mode and observing the console error when modifying the model value.

In your example, a shallow object can be spread into a new object as to avoid this conflict, for example:

computedObject() {
  return { ...this.$store.state.someList.at(-1) };
}
cuebit commented 1 year ago

Feel free to file a new issue if you discover anything else 👍