🙄
【Firebase】onShapshotを使ったらVuexに怒られた話
はじめに
FirebaseのメソッドonSnapshot
とVuex
を使用した際に、このようなエラーが出た。
Error: [vuex] do not mutate vuex store state outside mutation handlers.
stateは変更してないのに。。。という方も多いはず。
今回はこの原因と対処法についての記事です。
まずは、実際のコードがこちら
今回はチェックボックス付きのToDoリストを'snapshot'でリアルタイムに更新させるプロジェクトを作成してみました。
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>
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でリストを取得して、表示させているだけです。
ここまでは問題なく動いています。
これにタスクのチェックを変更する処理を追加してみる。
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 })
}
}
このチェックボックスを押すとこのエラーが登場する。
Error: [vuex] do not mutate vuex store state outside mutation handlers.
stateは変更していないはずなのに、何故なのか?
詳しいエラーの状況
- 先ほどのエラーが出ている状態でリロードしてみた。
- チェックの状態は問題なく反映されている。
- ここでチェックの更新処理ではなく、リストの再取得に問題があることがわかる。
- リストを取得している関数にコンソールを出して、どこで止まっているのかを確認してみた。
- チェックの値を更新した際に
onSnapshot
以降の処理しか動いていないことが判明。
つまりこういうこと
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)
はどうやって実行されているのか??
つまり犯人はこいつである。
const taskList = []
考察
2回目の自動取得の際に新たなtaskListが存在せず、state
のtaskListを見てしまっていたのではないか。この処理はactions
で行っているので、エラーの内容にある通り、mutations
以外でstate
の値を変更することになっていた??。
結論
こうすればOK
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
で使用している変数名と同じなのはバグが増えるだけなのでやめた方がいいです。
関数用の変数名を自分なりに用意しておいた方がいいかも。
Discussion