Jayson H
Jayson H

Reputation: 2048

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

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: {}
}

Upvotes: 10

Views: 17620

Answers (5)

inmyth
inmyth

Reputation: 9050

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)
},

Upvotes: 0

Javis Perez
Javis Perez

Reputation: 4350

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() });
    }
  })

Upvotes: -1

ianYa
ianYa

Reputation: 19

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

state.someStatehere = $lodash.cloneDeep(data)

Upvotes: 0

KF Lin
KF Lin

Reputation: 1313

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.

Upvotes: 19

gustavopch
gustavopch

Reputation: 931

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.

Upvotes: 11

Related Questions