Vue watch array (or object) push same old and new values

8.7k Views Asked by At

I'm using Vuex and I've created a module called claims that looks like this:

import to from 'await-to-js'
import { functions } from '@/main'
import Vue from 'vue'

const GENERATE_TX_SUCCESS = 'GENERATE_TX_SUCCESS'
const GENERATE_TX_ERROR = 'GENERATE_TX_ERROR'

export default {
  state: [ ],
  mutations: {
    [GENERATE_TX_SUCCESS] (state, generateTxData) {
      state.push({ transaction: { ...generateTxData } })
    },
    [GENERATE_TX_ERROR] (state, generateTxError) {
      state.push({ transaction: { ...generateTxError } })
    }
  },
  actions: {
    async generateTx ({ commit }, data) {
      const [generateTxError, generateTxData] = await to(functions.httpsCallable('generateTx')(data))
      if (generateTxError) {
        commit(GENERATE_TX_ERROR, generateTxError)
      } else {
        commit(GENERATE_TX_SUCCESS, generateTxData)
      }
    }
  },
  getters: { }
}

Then, in the .vue component I do have this watch:

 watch: {
    claims: {
      handler (newTxData, oldTxData) {
        console.log(newTxData)
      }
    }
 }

The problem I'm facing here is that newTxData is the same as oldTxData.

From my understanding, since this is a push and it detects the change, it's not one of these caveats: https://v2.vuejs.org/v2/guide/list.html#Caveats

So basically the problem is this one:

Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.

https://v2.vuejs.org/v2/api/#vm-watch

Then my question is: how should I workaround this in the mutation?

Edit:

I tried also with Vue.set(state, state.length, generateTxData) but got the same behaviour.

Edit 2 - temporary solution - (bad performance-wise):

I'm adapting @matthew (thanks to @Jacob Goh) to my solution with vuexfire:

computed: {
  ...mapState({
    claims: state => cloneDeep(state.claims)
  })
},
2

There are 2 best solutions below

3
On

You can assign state to a new object:

  mutations: {
    [GENERATE_TX_SUCCESS] (state, generateTxData) {
      state = [
         ...state,
         { transaction: { ...generateTxData } }
      ]
    },
    [GENERATE_TX_ERROR] (state, generateTxError) {
      state = [
         ...state,
         { transaction: { ...generateTxError } }
      ]
    }
  }
3
On

This answer is based on this pretty smart answer by @matthew

You will need lodash cloneDeep function

Basically, create a computed value like this

computed: {
    claimsForWatcher() {
        return _.cloneDeep(this.claims);
    }
}

What happens is, everytime a new value is pushed into claims, claimsForWatcher will become an entirely new object.

Therefore, when you watch claimsForWatcher, you won't have the problem where 'the old value will be the same as new value because they reference the same Object/Array' anymore.

watch: {
    claimsForWatcher(oldValue, newValue) {
        // now oldValue and newValue will be 2 entirely different objects
    }
}

This works for ojects as well.

The performance hit is exaggerated for the real life use cases till you probably have hundreds (nested?) properties (objects) in your cloning object.

Either with clone or cloneDeep the clone takes 1/10 of a millisecond as reported with window.performance.now(). So measured under Node with process.hrtime() it could show even lower figures.