Home » vue » Why are v-model inputs in my Vue app triggering mutation errors on change?

Why are v-model inputs in my Vue app triggering mutation errors on change?

Posted by: admin November 26, 2021 Leave a comment

Questions:

I have some V-Model inputs with a local data object:

  • If I send the local filter object as the payload of my action everything works without errors.
  • If I first store the filter object in the vuex store, and then pass it to my action as the payload, It also works however if I then change any input in the component I get a Vuex mutation error.

Why is changing the v-model inputs triggering a mutation at all? How do I store this components filter object in the vuex store and use it as the payload of my filtering action?

<template>
  <div class="q-pa-md q-mb-md bg-grey-2 rounded-borders">
    <div class="row q-gutter-md items-start justify-start">
      <q-select
        :options="typeOptions"
        dense
        outlined
        transition-hide="flip-down"
        transition-show="flip-up"
        v-model="filter.type"
        style="min-width: 120px;"
      />

      <q-input dense outlined v-model="filter.startLastSeenTime" mask="date">
        <template v-slot:append>
          <q-icon name="event" class="cursor-pointer">
            <q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
              <q-date v-model="filter.startLastSeenTime" @input="() => $refs.qDateProxy.hide()" />
            </q-popup-proxy>
          </q-icon>
        </template>
      </q-input>

      <q-input dense outlined v-model="filter.endLastSeenTime" mask="date">
        <template v-slot:append>
          <q-icon name="event" class="cursor-pointer">
            <q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
              <q-date v-model="filter.endLastSeenTime" @input="() => $refs.qDateProxy.hide()" />
            </q-popup-proxy>
          </q-icon>
        </template>
      </q-input>

      <q-input
        dense
        outlined
        placeholder="Keyword search"
        type="search"
        v-model="filter.textSearch"
      >
        <template v-slot:append>
          <q-icon name="search" />
        </template>
      </q-input>

      <q-input
        dense
        outlined
        placeholder="Browser name"
        v-model="filter.browser"
      />

      <q-input
        dense
        outlined
        placeholder="Device ID"
        v-model="filter.deviceId"
      />

      <q-input
        dense
        outlined
        placeholder="Tracking ID"
        v-model="filter.trackingId"
      />

      <q-input
        dense
        outlined
        placeholder="Fingerprint"
        v-model="filter.fingerprint"
      />

      <q-input
        dense
        outlined
        placeholder="IP address"
        v-model="filter.ipAddress"
      />

      <q-input
        dense
        outlined
        placeholder="Installation ID"
        v-model="filter.installationId"
      />

      <q-input
        dense
        outlined
        placeholder="Hook name"
        v-model="filter.hookName"
      />

      <q-input
        dense
        outlined
        placeholder="Channel ID"
        v-model="filter.channelId"
      />

      <q-checkbox
        color="primary"
        class="self-center"
        dense
        label="Forensics Active"
        v-model="filter.forensicsActive"
      />

      <q-space />

      <q-btn @click="resetFilter();" label="reset" color="grey" />
      <q-btn @click="filterDevices();" label="Filter" color="primary" class="q-px-md" />
    </div>
  </div>
</template>

<script>
export default {
  name: 'Filters',
  data() {
    return {
      typeOptions: [
        'Android', 'iOS', 'Web',
      ],
      filter: {
        type: 'ANDROID',
        page: 0,
        forensicsActive: false,
        textSearch: '',
        browser: '',
        deviceId: '',
        trackingId: '',
        fingerprint: '',
        ipAddress: '',
        installationId: '',
        hookName: '',
        channelId: '',
        startLastSeenTime: '',
        endLastSeenTime: '',
      },
    };
  },
  methods: {
    resetFilter() {
      Object.keys(this.filter).forEach((key) => {
        this.filter[key] = '';
      });
      this.filter.forensicsActive = false;
      this.filter.page = 0;
      this.$store.commit('Devices/UPDATE_ACTIVE_FILTER', this.filter);
      this.$store.dispatch('Devices/filterDevices', this.$store.getters['Devices/activeFilter']);
    },
    filterDevices() {
      // this works also! but after the first time it runs any change to the inputs in this component throws a vuex mutation error
      // this.$store.commit('Devices/UPDATE_ACTIVE_FILTER', this.filter);
      // this.$store.dispatch('Devices/filterDevices', this.$store.getters['Devices/activeFilter']);

      // this works without errors
      this.$store.dispatch('Devices/filterDevices', this.filter);
    },
  },
};
</script>

State: (Vuex module)

export default function () {
  return {
    activeFilter: {
      browser: '',
      channelId: '',
      deviceId: '',
      endLastSeenTime: '',
      fingerprint: '',
      forensicsActive: false,
      hookName: '',
      installationId: '',
      ipAddress: '',
      page: 0,
      startLastSeenTime: '',
      textSearch: '',
      trackingId: '',
      type: 'ANDROID',
    },
    devicesList: {},
    deviceToView: [],
  };
}

The error:

[Vue warn]: Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] do not mutate vuex store state outside mutation handlers."

(found in <Root>)
warn @ vue.runtime.esm.js?5593:619
logError @ vue.runtime.esm.js?5593:1884
globalHandleError @ vue.runtime.esm.js?5593:1879
handleError @ vue.runtime.esm.js?5593:1839
run @ vue.runtime.esm.js?5593:4570
update @ vue.runtime.esm.js?5593:4542
notify @ vue.runtime.esm.js?5593:730
reactiveSetter @ vue.runtime.esm.js?5593:1055
set @ vue.runtime.esm.js?5593:1077
callback @ Filters.vue?d4cd:25
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
Vue.$emit @ vue.runtime.esm.js?5593:3888
Vue.<computed> @ backend.js:1793
toggleOption @ QSelect.js?9e66:449
click @ QSelect.js?9e66:282
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
Vue.$emit @ vue.runtime.esm.js?5593:3888
Vue.<computed> @ backend.js:1793
__onClick @ QItem.js?fe67:97
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
original._wrapper @ vue.runtime.esm.js?5593:6917
vue.runtime.esm.js?5593:1888 Error: [vuex] do not mutate vuex store state outside mutation handlers.
    at assert (vuex.esm.js?94e4:94)
    at Vue.store._vm.$watch.deep (vuex.esm.js?94e4:834)
    at Watcher.run (vue.runtime.esm.js?5593:4568)
    at Watcher.update (vue.runtime.esm.js?5593:4542)
    at Dep.notify (vue.runtime.esm.js?5593:730)
    at Object.reactiveSetter [as type] (vue.runtime.esm.js?5593:1055)
    at Proxy.set (vue.runtime.esm.js?5593:1077)
    at callback (Filters.vue?d4cd:25)
    at invokeWithErrorHandling (vue.runtime.esm.js?5593:1854)
    at VueComponent.invoker (vue.runtime.esm.js?5593:2179)
logError @ vue.runtime.esm.js?5593:1888
globalHandleError @ vue.runtime.esm.js?5593:1879
handleError @ vue.runtime.esm.js?5593:1839
run @ vue.runtime.esm.js?5593:4570
update @ vue.runtime.esm.js?5593:4542
notify @ vue.runtime.esm.js?5593:730
reactiveSetter @ vue.runtime.esm.js?5593:1055
set @ vue.runtime.esm.js?5593:1077
callback @ Filters.vue?d4cd:25
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
Vue.$emit @ vue.runtime.esm.js?5593:3888
Vue.<computed> @ backend.js:1793
toggleOption @ QSelect.js?9e66:449
click @ QSelect.js?9e66:282
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
Vue.$emit @ vue.runtime.esm.js?5593:3888
Vue.<computed> @ backend.js:1793
__onClick @ QItem.js?fe67:97
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
original._wrapper @ vue.runtime.esm.js?5593:6917

I tried everything I could read about all day including https://vuex.vuejs.org/guide/forms.html and lodash clone but I still don’t understand why changing the inputs without even firing my method is causing a mutation to occur.

Answers:

You can use the v-model directive with data stored in Vuex only if you set two-way computed properties as described in the doc.

UPDATE

According to what I understand from the comments, you should change your mutation to something like this:

export default {
    // ...
    state: {
        activeFilter: {
            browser: '',
            channelId: '',
            deviceId: '',
            endLastSeenTime: '',
            fingerprint: '',
            forensicsActive: false,
            hookName: '',
            installationId: '',
            ipAddress: '',
            page: 0,
            startLastSeenTime: '',
            textSearch: '',
            trackingId: '',
            type: 'ANDROID',
        }
    },
    mutations: {
        // ...
        UPDATE_ACTIVE_FILTER(state, payload) {
            Object.keys(payload).forEach(filterKey => {
                state.activeFilter[filterKey] = payload[filterKey];
            });
        }
    }
}

###

You shouldn’t use mutations directly in components.

This is what you should be using instead of this.$state.commit and this.$state.dispatch

https://vuex.vuejs.org/guide/actions.html

https://vuex.vuejs.org/guide/actions.html#dispatching-actions-in-components

This is what you should be using instead of this.$state.getters

https://vuex.vuejs.org/guide/getters.html

https://vuex.vuejs.org/guide/getters.html#the-mapgetters-helper