简体   繁体   中英

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

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.

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.

我也有这个问题,我用 lodash 来克隆数据

state.someStatehere = $lodash.cloneDeep(data)

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM