简体   繁体   中英

Error: Do not mutate vuex store state outside mutation handlers

Scenario
I am using Vuex, to store some data in it, and in my case the ticket details. Initially, I have a ticket which has an array of discounts, to be empty. Once I hit the button "Add discount" I mount the component called "testDiscount" which in the mounted hook pushes the first object ({"code": "Foo", "value":"Boo"}) in the discounts array of a specific ticket in the store. The problem arise when I try to type in the input boxes (changing its state) in this component where I get the error "do not mutate Vuex store state outside mutation handlers.". How could I best handle this?

Test.vue

<template>
  <div>
    <test-component v-for="(t, key) in tickets" :key="key" :ticket-key="key" :tid="t.id"></test-component>
  </div>
</template>

<script>
import TestComponent from "~/components/testComponent.vue";
export default {
  layout: "noFooter",
  components: {
    "test-component": TestComponent,
  },

  data() {
    return {
      tickets: this.$store.state.ticketDiscount.tickets,
    };
  },

  mounted() {
    if (this.tickets.length == 0) {
      this.$store.commit("ticketDiscount/addTicket", {
        id:
          this.$store.state.ticketDiscount.tickets.length == 0
            ? 0
            : this.$store.state.ticketDiscount.tickets[
                this.$store.state.ticketDiscount.tickets.length - 1
              ].id + 1,
        discount: [],
      });
    }
  },
};
</script>

ticketDiscount.js

export const state = () => ({
    tickets: []
});

export const mutations = {
    addTicket(state, ticket) {
        state.tickets.push(ticket);
    },

    addDiscount(state, property) {
        state.tickets.find(ticket => ticket.id == property.id)[property.name].push(property.value);
    }

testComponent.vue

<template>
  <div>
    <h3>Ticket number: {{ticketKey + 1}}</h3>
    <button @click="showDiscount = true">Add discount</button>
    <test-discount v-model="discount_" v-if="showDiscount" :tid="tid"></test-discount>
  </div>
</template>

<script>
import testDiscount from "~/components/test-discount.vue";
export default {
  components: {
    testDiscount,
  },

  data() {
    return {
      showDiscount: false,
      tid_: this.tid,
    };
  },
  props: {
    tickets: Array,
    ticketKey: { type: Number },
    tid: { type: Number, default: 0 },
  },

  methods: {
    updateTicket() {
      this.$emit("updateTicket", {
        id: this.tid_,
        value: {
          discount: this.discount_,
        },
      });
    },
  },

  mounted() {
    this.$watch(
      this.$watch((vm) => (vm.discount_, Date.now()), this.updateTicket)
    );
  },

  computed: {
    discount_: {
      get() {
        return this.$store.state.ticketDiscount.tickets.find(
          (ticket) => ticket.id == this.tid
        )["discount"];
      },

      set(value) {
        // set discount
      },
    },
  },
};
</script>

testDiscount.vue

<template>
<div class="container">
        <div class="title">
            <img src="~/assets/svgs/price_tag.svg" />
            <span>Discount code</span>
            {{ discounts }}
        </div>
            
        <div class="discount-container">
            <div v-for="(c,idx) in discounts" class="discounts" :key="idx">
                <div class="perc-input">
                    <input style="max-width: 50px;" v-model.number="c.discount" type="number" min="1" max="100" step="1" placeholder="10">
                    <div>%</div>
                </div>
                <input class="code-input" v-model="c.code" placeholder="Code">
                <img src="~/assets/svgs/bin.svg" title="Delete code" @click="deleteCode(idx)" v-if="discounts.length > 1"/>
            </div>
        </div>

        <span @click="newDiscount" class="add-another">+ Add another discount</span>
    </div>        
</template>

<script>
export default {
    props: {
        value: {
            type: Array,
        },
        tid: { type: Number, default: 0 },
    },
    data() {
        return {
            discounts: this.value,
        }
    },
    mounted() {
        if (this.discounts.length == 0) {
            this.newDiscount();
        }
    },
    methods: {
        newDiscount() {
            this.$store.commit('ticketDiscount/addDiscount',
            {   
                "id": this.tid,
                "name": "discount",
                "value": { code: null,discount: null }
            });
        },
        deleteCode(index) {
            this.discounts.splice(index, 1);
        }
    },
    watch: {
        discounts() {
            this.$emit('input', this.discounts)
        }
    },
    beforeDestroy() {
        this.$emit('input', []);
    }
}
</script>

you shouldn't use v-model in this case.

<input style="max-width: 50px;" v-model.number="c.discount" .../>

you could just set the value

<input style="max-width: 50px;" :value="c.discount" @change="handleValueChange" .../>

and then in handleValueChange function to commit the action to update just for that value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM