Vuex & VueJS (Do not mutate vuex store state outside mutation handlers)
Asked Answered
T

5

10

I'm trying to create a listenAuth function that watches "onAuthStateChanged" in firebase to notify the vuex store when a user has logged in or out. As far as I can tell, I'm only modifying state.authData using the mutation handler, unless I'm missing something?

I'm getting the error:

[vuex] Do not mutate vuex store state outside mutation handlers.

Here's my App.vue javascript (from my component)

<script>
// import Navigation from './components/Navigation'
import * as actions from './vuex/actions'
import store from './vuex/store'
import firebase from 'firebase/app'

export default {
  store,
  ready: function () {
    this.listenAuth()
  },
  vuex: {
    actions,
    getters: {
      authData: state => state.authData,
      user: state => state.user
    }
  },
  components: {
    // Navigation
  },
  watch: {
    authData (val) {
      if (!val) {
        this.redirectLogin
        this.$route.router.go('/login')
      }
    }
  },
  methods: {
    listenAuth: function () {
      firebase.auth().onAuthStateChanged((authData) => {
        this.changeAuth(authData)
      })
    }
  }
}
</script>

Here's my action (changeAuth) function

export const changeAuth = ({ dispatch, state }, authData) => {
  dispatch(types.AUTH_CHANGED, authData)
}

Here's my store (the parts that matter)

const mutations = {
  AUTH_CHANGED (state, authData) {
    state.authData = authData
  }
}

const state = {
  authData: {}
}
Tolkan answered 14/7, 2016 at 3:40 Comment(1)
You may want to upgrade to Vuex 2.0Repudiation
C
19

I also came across this issue. My store:

  state: {
    items: []
  },
  mutations: {
    SetItems (state, payload) {
      // Warning
      state.items = payload.items
    }
  },
  actions: {
    FetchItems ({commit, state}, payload) {
      api.getItemsData(payload.sheetID)
        .then(items => commit('SetItems', {items}))
    }
  }

Fixed it by replace state.items = payload.items with:

state.items = payload.items.slice()

The reason is that arrays are stored as references in Javascript and payload.items is likely to be changed outside Vuex. So we should just use a fresh copy of payload.items instead.

For state objects, use:

state.someObj = Object.assign({}, payload.someObj)

And don't use JSON.parse(JSON.stringify(someObj)) as it's much slower.

Creech answered 19/5, 2017 at 18:1 Comment(0)
T
11

After struggling with the same problem, I found that the error only happens when we try to store the auth/user data in the Vuex state.

Changing from...

const mutations = {
  AUTH_CHANGED (state, authData) {
    state.authData = authData
  }
}

...to...

const mutations = {
  AUTH_CHANGED (state, authData) {
    state.authData = JSON.parse(JSON.stringify(authData))
  }
}

would solve your case.

Tiberias answered 18/5, 2017 at 14:54 Comment(6)
Use state.authData = Object.assign({}, authData) instead. Have a look at my answer below.Creech
For some reason Object.assign({}, authData) doens't work, but JSON.parse(JSON.stringify(authData))does.Hagiarchy
the reason Object.assign doesn't work sometimes is because it doesn't perform a deep copy, as described in the doc "For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value." . for deep clone, JSON.parse(JSON.stringify(obj)) is better, even if it will certainly be slowerIslam
upvoted for the alternative suggested, works for my auth as wellCentripetal
I'd like to ask why does this solution work since there is no explanation.Exclude
@Exclude That's a good question. It's been 4 years since I wrote this answer and I don't have any idea why it works anymore. Self-reminder: next time, actually explain stuff instead of just providing the code.Tiberias
E
0

I had this issue too, and I used lodash to clone data

state.someStatehere = $lodash.cloneDeep(data)
Exhaustion answered 4/7, 2019 at 9:12 Comment(0)
H
0

So from many answers we can agree on the cause: the object stored in the state is silently modified by something that holds reference to it. And many suggest making a copy of the object.

The other alternative is to empty the state while the object is being modified. This works when for some reason you cannot copy the object. For example:

bla({ state } {}) {
  mutatingMethod(state.xxx);
},

If you know mutatingMethod is modifying xxx then you can do something like this:

bla({ state } {}) {
  let xxx = state.xxx
  commit("SET_XXX", null)
  mutatingMethod(xxx);
  commit("SET_XXX", xxx)
},
Hunkers answered 8/7, 2021 at 4:56 Comment(0)
D
-1

For anybody who's also struggling with this, this is another way to fix it (what actually worked for me):

auth()
  .onAuthStateChanged((user) => {
    if (user) {
      commit(MUTATION_TYPES.SET_USER, { ...user.toJSON() });
    }
  })
Dorkus answered 13/8, 2020 at 22:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.