【Firebase】onShapshotを使ったらVuexに怒られた話

## はじめに
Firebaseのメソッド`onSnapshot`と`Vuex`を使用した際に、このようなエラーが出た。

```console
Error: [vuex] do not mutate vuex store state outside mutation handlers.
```
stateは変更してないのに。。。という方も多いはず。

今回はこの原因と対処法についての記事です。


## まずは、実際のコードがこちら
今回はチェックボックス付きのToDoリストを'snapshot'でリアルタイムに更新させるプロジェクトを作成してみました。

```vue:index.vue
<template>
  <div>
    <div v-for="(item, index) in taskList" :key="index">
      <input
        type="checkbox"
        :checked="item.isChecked"
        @click="toggleCheck(item.isChecked, item.id)"
      >
    </div>
  </div>
</template>

<script>
export default {
  ...mapState({
    taskList: state => state.taskList
  })
  created() {
    this.$store.dispatch('task/fetchTaskList')
  }
  methods: {
    toggleCheck(isChecked, taskId){
      this.$store.dispatch('task/toggleCheck', { isChecked: isChecked, taskId: taskId })
    }
  }
}
</script>
```

```js:store/task.js
export const state = () => ({
  taskList: []
})

export const mutations = {
  setTaskList(state, list) {
    state.taskList = list
  }
}

export const actions = {
  fetchTaskList({ commit, rootState }) {
    const taskList = []
    this.$firestore()
      .collection('user')
      .doc(rootState.sign.uid)
      .collection('taskList')
      .onSnapshot(async querySnapshot => {
        await querySnapshot.forEach(doc => {
          const task = Object.assign(doc.data(), { id: doc.id })
          taskList.push(task)
        })
        commit('setTaskList', taskList)
      })
  }
}
```
簡単に言うと、snapshotでリストを取得して、表示させているだけです。
ここまでは問題なく動いています。


これにタスクのチェックを変更する処理を追加してみる。

```js:store/task.js
async toggleDone({ rootState }, { isDone, taskId }) {
    const newTask = {
      isDone: isDone ? false : true
    }
    await this.$firestore()
      .collection('user')
      .doc(rootState.sign.uid)
      .collection('taskList')
      .doc(taskId)
      .set(newTask, { merge: true })
  }
}
```

このチェックボックスを押すとこのエラーが登場する。

```console
Error: [vuex] do not mutate vuex store state outside mutation handlers.
```
stateは変更していないはずなのに、何故なのか？

## 詳しいエラーの状況
1. 先ほどのエラーが出ている状態でリロードしてみた。
  - チェックの状態は問題なく反映されている。
  - ここでチェックの更新処理ではなく、リストの再取得に問題があることがわかる。
   
2. リストを取得している関数にコンソールを出して、どこで止まっているのかを確認してみた。
  - チェックの値を更新した際に`onSnapshot`以降の処理しか動いていないことが判明。

## つまりこういうこと
```js:store/task.js
export const actions = {
  fetchTaskList({ commit, rootState }) {
    const taskList = []
    this.$firestore()
      .collection('user')
      .doc(rootState.sign.uid)
      .collection('taskList')
// firestoreの値が更新された際、ここから下の処理が自動で動く
      .onSnapshot(async querySnapshot => {
        await querySnapshot.forEach(doc => {
          const task = Object.assign(doc.data(), { id: doc.id })
          taskList.push(task)
        })
        commit('setTaskList', taskList)
      })
  }
}
```
`onSnapshot`移行しか処理が実行されていないとしたら、`taskList.push(task)`はどうやって実行されているのか？？

つまり犯人はこいつである。

```js
const taskList = []
```

## 考察
2回目の自動取得の際に新たなtaskListが存在せず、`state`のtaskListを見てしまっていたのではないか。この処理は`actions`で行っているので、エラーの内容にある通り、`mutations`以外で`state`の値を変更することになっていた??。


## 結論
こうすればOK

```js:store/task.js
export const actions = {
  fetchTaskList({ commit, rootState }) {
    this.$firestore()
      .collection('user')
      .doc(rootState.sign.uid)
      .collection('taskList')
// firestoreの値が更新された際、ここから下の処理が自動で動く
      .onSnapshot(async querySnapshot => {
        const taskList = []
        await querySnapshot.forEach(doc => {
          const task = Object.assign(doc.data(), { id: doc.id })
          taskList.push(task)
        })
        commit('setTaskList', taskList)
      })
  }
}
```
`taskList`の定義を`onSnapshot`の中に入れたら正常に動作した。


## そもそも...
そもそも関数内で定義している変数名が`state`で使用している変数名と同じなのはバグが増えるだけなのでやめた方がいいです。
関数用の変数名を自分なりに用意しておいた方がいいかも。