0

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.

2 Answers2

2

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];
            });
        }
    }
}
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Gaetan C.
  • 1,742
  • 13
  • 21
  • This is the part I am confused about. I thought the components local data was separate from the vuex one. My intention was to take the local filter object wich is v-model bound to my inputs, and send that to the store as the payload of my action. What am I missing here? – Daniel Toillion Jul 02 '20 at 14:49
  • Indeed, local data and vuex data are separate. My solution was to get rid of the local data and directly edit Vuex data. If for some reasons, you want to have a local copy of the vuex data in your component (and not actually using two-way computed properties), you can also do it but are probably missing something. Please post the code of your store so that I can help you. – Gaetan C. Jul 02 '20 at 15:09
  • If you see my filter method above, it is taking the local filter object and passing it as the payload of my action. This works as expected without errors. The commented out portion does the same thing but it first stores the filter object in the state and retrieves it with a getter as the payload of the action. BOTH of these are working. BUT where the getter is used as payload, any subsequent touches on the v-model inputs trigger the error, even though everything still works as expected. Why does manipulating the local data object cause the error before i even trigger a mutation? – Daniel Toillion Jul 02 '20 at 15:37
  • I think that your problem lies in your `UPDATE_ACTIVE_FILTER` mutation. When you assign your filter attribute (vuex side) with your local filter, you basically just copy the reference to this object. As they are pointing to the same object, the `v-model` directives trigger this error. I can help you fixing that if you need too, just post the code of this mutation. – Gaetan C. Jul 02 '20 at 17:08
  • export function UPDATE_ACTIVE_FILTER(state, payload) { state.activeFilter = payload; } The local `this.filter` object is passed as the payload when you click the filter button. Then it dispatches an action, and that action's payload is the getter for state.activeFilter. It all works as expected updating the state with the new filter and making the request to the API. But v-model updates to the local filter objet trigger the error on change before this mutation is even fired. – Daniel Toillion Jul 03 '20 at 07:12
  • I edited my post, let me know if that solves your problem. – Gaetan C. Jul 03 '20 at 07:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/217130/discussion-between-daniel-toillion-and-gaetan-c). – Daniel Toillion Jul 03 '20 at 08:18
-1

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

  • The component uses commit and dispatch. The data is rendered in a different component using map getters. The error message is confusing because it was actually caused by the mutation copying a whole new object instead of updating the values of the current one. – Daniel Toillion Jul 09 '20 at 07:09