tazboy
tazboy

Reputation: 1754

Vuex - 'do not mutate vuex store state outside mutation handlers'

I'm trying to initialize my Vuex store from Firestore. The last line of code context.commit('SET_ACTIVITIES', acts) is what creates the error. I don't think I'm mutating the state directly since I'm using an action. What could I be missing?

Here's my Vuex store:

export default new Vuex.Store({
  strict: true,
  state: {
    activities: []
  },
  mutations: {
    SET_ACTIVITIES: (state, activities) => {
      state.activities = activities
    },
  },

  actions: {
    fetchActivities: context => {
      let acts = []
      let ref = db.collection('activities')
      ref.onSnapshot(snapshot => {
        snapshot.docChanges().forEach(change => {
          if(change.type == 'added') {
            acts.push({
              id: change.doc.id,
              name: change.doc.data().name,
              day: change.doc.data().day
            })
          }
        })
      })
      context.commit('SET_ACTIVITIES', acts)
    }
  }

Also, it gives me the error equal to the number of items in Firestore. Why would it do that if I'm only doing one commit?

console:

[Vue warn]: Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] do not mutate vuex store state outside mutation handlers."

and

Error: [vuex] do not mutate vuex store state outside mutation handlers.
    at assert (vuex.esm.js?2f62:87)
    at Vue.store._vm.$watch.deep (vuex.esm.js?2f62:763)
    at Watcher.run (vue.runtime.esm.js?2b0e:4562)
    at Watcher.update (vue.runtime.esm.js?2b0e:4536)
    at Dep.notify (vue.runtime.esm.js?2b0e:730)
    at Array.mutator (vue.runtime.esm.js?2b0e:882)
    at eval (store.js?c0d6:36)
    at eval (index.cjs.js?e89a:21411)
    at eval (index.cjs.js?e89a:4904)
    at LLRBNode.inorderTraversal (index.cjs.js?e89a:1899)

Upvotes: 6

Views: 8907

Answers (1)

Phil
Phil

Reputation: 165062

You're running into an issue with object references and asynchronous methods.

CollectionReference#onSnapshot() is asynchronous, triggering the snapshot listener / observer on QuerySnapshot events.

Basically what happens in your code is that you assign the empty array acts to state.activities (same object reference) in your mutation and then, at a later time in your snapshot event handler, directly push elements into it.

A quick solution would be to commit the mutation within the onSnapshot observer

fetchActivities: context => {
  let ref = db.collection('activities')
  ref.onSnapshot(snapshot => {
    let acts = []
    snapshot.docChanges().forEach(change => {
      if(change.type == 'added') {
        acts.push({
          id: change.doc.id,
          name: change.doc.data().name,
          day: change.doc.data().day
        })
      }
    })
    context.commit('SET_ACTIVITIES', acts)
  })
}

If you only want to do an initial fetch of your collection data, use CollectionReference#get() instead. Given it returns a promise, you can use this to make your action composable

async fetchActivities ({ commit }) {
  let snapshot = await db.collection('activities').get()
  let acts = snapshot.docChanges().filter(({ type }) => type === 'added')
      .map(({ doc }) => ({
        id: doc.id,
        name: doc.data().name,
        day: doc.data().day
      }))
  commit('SET_ACTIVITIES', acts)
}

Upvotes: 9

Related Questions