Reputation: 87
I have gone through the other "DOM not updating" posts, but have not been able to figure out a solution. I am trying to create a task app for starters and the app can already load from Firestore, add a new one and delete one. I have two problems, but I would like to start with the first one. When I add, delete data, the DOM is not updating dynamically, although the respective array in the Vuex store is updated correctly. When I reload the page, it is also updating.
I have added the code of the component and the store, I hope I am looking the in the right place, but I have the feeling I am not.
Thanks in advance
Store code:
import Vue from 'vue'
import Vuex from 'vuex'
// import firebase from 'firebase'
import router from '@/router'
import db from '@/db'
import firebase from '@/firebase'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
appTitle: 'My Awesome App',
addedTask: false,
loadedTasks: false,
deletedTasks: false,
user: null,
error: null,
loading: false,
tasks: [],
task: {
title: '',
description: '',
tags: [],
dueTime: '',
dueDate: '',
reminderFlag: Boolean,
quadrant: '',
userId: '',
timestamp: ''
}
},
// Mutations (synchronous): change data in the store
mutations: {
setUser (state, payload) {
state.user = payload
state.task.userId = firebase.auth().currentUser.uid
},
setError (state, payload) {
state.error = payload
},
setLoading (state, payload) {
state.loading = payload
},
addedTask (state, payload) {
state.addedTask = payload
},
loadedTasks (state, payload) {
state.loadedTasks = true
},
deletedTask (state, payload) {
state.deletedTask = payload
}
},
// Actions (asynchronous/synchronous): change data in the store
actions: {
autoSignIn ({ commit }, payload) {
commit('setUser', { email: payload.email })
},
userSignUp ({ commit }, payload) {
commit('setLoading', true)
firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password)
.then(firebaseUser => {
commit('setUser', { email: firebaseUser.user.email })
commit('setLoading', false)
router.push('/home')
commit('setError', null)
})
.catch(error => {
commit('setError', error.message)
commit('setLoading', false)
})
},
userSignIn ({ commit }, payload) {
commit('setLoading', true)
firebase.auth().signInWithEmailAndPassword(payload.email, payload.password)
.then(firebaseUser => {
commit('setUser', { email: firebaseUser.user.email })
commit('setLoading', false)
commit('setError', null)
router.push('/home')
})
.catch(error => {
commit('setError', error.message)
commit('setLoading', false)
})
},
userSignOut ({ commit }) {
firebase.auth().signOut()
commit('setUser', null)
router.push('/')
},
addNewTask ({ commit }, payload) {
commit('addedTask', false)
db.collection('tasks').add({
title: payload.title,
userId: firebase.auth().currentUser.uid,
createdOn: firebase.firestore.FieldValue.serverTimestamp(),
description: payload.description,
dueDateAndTime: new Date(payload.dueDate & ' ' & payload.dueTime),
reminderFlag: payload.reminderFlag,
quadrant: payload.quadrant,
tags: payload.tags
})
.then(() => {
commit('addedTask', true)
this.state.tasks.push(payload)
commit('loadedTasks', true)
})
.catch(error => {
commit('setError', error.message)
})
},
getTasks ({ commit }) {
commit('loadedTasks', false)
db.collection('tasks').orderBy('createdOn', 'desc').get().then(querySnapshot => {
querySnapshot.forEach(doc => {
const data = {
'taskId': doc.id,
'title': doc.data().title,
'quadrant': doc.data().quadrant
}
this.state.tasks.push(data)
})
commit('loadedTasks', true)
})
},
deleteTask ({ commit }, payload) {
db.collection('tasks').doc(payload).delete().then(() => {
this.state.tasks.pop(payload)
commit('deletedTask', true)
commit('loadedTasks', true)
}).catch(function (error) {
console.error('Error removing document: ', error)
commit('deletedTask', false)
})
}
},
// Getters: receive data from the store
getters: {
isAuthenticated (state) {
return state.user !== null && state.user !== undefined
}
}
})
This is the component (Vue app -> Home -> Grid -> Tasklist (this):
<template>
<v-card>
<v-card-title primary class="title">{{title}}</v-card-title>
<v-flex v-if="tasks.length > 0">
<v-slide-y-transition class="py-0" group tag="v-list">
<template v-for="(task, i) in tasks" v-if="task.quadrant === Id">
<v-divider v-if="i !== 0" :key="`${i}-divider`"></v-divider>
<v-list-tile :key="`${i}-${task.text}`">
<v-list-tile-action>
<v-checkbox v-model="task.done" color="info darken-3">
<div slot="label" :class="task.done && 'grey--text' || 'text--primary'" class="text-xs-right" v-text="task.title +' ('+ task.taskId +')'"></div>
</v-checkbox>
</v-list-tile-action>
<v-spacer></v-spacer>
<v-scroll-x-transition>
<v-btn flat icon color="primary" v-show="!deleteConfirmation" @click="deleteConfirmation = !deleteConfirmation">
<v-icon>delete</v-icon>
</v-btn>
</v-scroll-x-transition>
<v-scroll-x-transition>
<v-btn flat icon color="green" v-show="deleteConfirmation" @click="deleteConfirmation = !deleteConfirmation">
<v-icon>cancel</v-icon>
</v-btn>
</v-scroll-x-transition>
<v-scroll-x-transition>
<v-btn flat icon color="red" v-show="deleteConfirmation" @click="deleteConfirmation = !deleteConfirmation; deleteTask(task.taskId)">
<v-icon>check_circle</v-icon>
</v-btn>
</v-scroll-x-transition>
</v-list-tile>
</template>
</v-slide-y-transition>
</v-flex>
</v-card>
</template>
<script>
export default {
data () {
return {
deleteConfirmation: false
}
},
props: ['Id', 'title'],
computed: {
tasks () {
return this.$store.state.tasks
}
},
methods: {
deleteTask (taskId) {
console.log(taskId)
this.$store.dispatch('deleteTask', taskId)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.container {
max-width: 1200px;
}
</style>
Upvotes: 1
Views: 1211
Reputation: 87
the DOM now updates on deletes properly. I changes the structure of the components a little bit. It is now: Vue app -> Home -> Grid -> Tasklist -> TaskListItem. I am still facing the following issue: Adding an item updates the DOM, but when I add multiple entries, e.g. with different titles, they are written to Firestore correctly, but in the DOM it shows them all as identical until I do a complete reload.
Please find the code below:
Store:
import Vue from 'vue'
import Vuex from 'vuex'
// import firebase from 'firebase'
import router from '@/router'
import db from '@/db'
import firebase from '@/firebase'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
appTitle: 'My Awesome App',
taskFormVisible: false,
addedTask: false,
loadedTasks: false,
deletedTasks: false,
user: null,
error: null,
loading: false,
tasks: [],
task: {
title: '',
description: '',
tags: [],
dueTime: '',
dueDate: '',
reminderFlag: Boolean,
quadrant: '',
userId: '',
timestamp: ''
}
},
// Mutations (synchronous): change data in the store
mutations: {
setUser (state, payload) {
state.user = payload
state.task.userId = firebase.auth().currentUser.uid
},
setError (state, payload) {
state.error = payload
},
setLoading (state, payload) {
state.loading = payload
},
addedTask (state, payload) {
state.addedTask = payload
},
loadedTasks (state, payload) {
state.loadedTasks = true
},
deletedTask (state, payload) {
state.deletedTask = payload
},
DELETE_TASK (state, payload) {
state.tasks = state.tasks.filter(task => {
return task.taskId !== payload
})
},
ADD_NEW_TASK (state, payload) {
// algorithm to remove, e. g. by id
// console.log(payload)
state.tasks.push(payload)
/* state.tasks = state.tasks.filter(e => {
return e.id !== payload
}) */
},
GET_TASKS (state, payload) {
state.tasks = payload
},
MAKE_TASKFORM_VISIBLE (state, payload) {
state.taskFormVisible = payload
}
},
// Actions (asynchronous/synchronous): change data in the store
actions: {
autoSignIn ({ commit }, payload) {
commit('setUser', { email: payload.email })
},
userSignUp ({ commit }, payload) {
commit('setLoading', true)
firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password)
.then(firebaseUser => {
commit('setUser', { email: firebaseUser.user.email })
commit('setLoading', false)
router.push('/home')
commit('setError', null)
})
.catch(error => {
commit('setError', error.message)
commit('setLoading', false)
})
},
userSignIn ({ commit }, payload) {
commit('setLoading', true)
firebase.auth().signInWithEmailAndPassword(payload.email, payload.password)
.then(firebaseUser => {
commit('setUser', { email: firebaseUser.user.email })
commit('setLoading', false)
commit('setError', null)
router.push('/home')
})
.catch(error => {
commit('setError', error.message)
commit('setLoading', false)
})
},
userSignOut ({ commit }) {
firebase.auth().signOut()
commit('setUser', null)
router.push('/')
},
makeTaskformVisible ({ commit }) {
if (this.state.taskFormVisible === false) {
commit('MAKE_TASKFORM_VISIBLE', true)
} else {
commit('MAKE_TASKFORM_VISIBLE', false)
}
},
addNewTask ({ commit }, payload) {
db.collection('tasks').add({
title: payload.title,
userId: firebase.auth().currentUser.uid,
createdOn: firebase.firestore.FieldValue.serverTimestamp(),
description: payload.description,
dueDateAndTime: payload.dueTimestamp,
reminderFlag: payload.reminderFlag,
quadrant: payload.quadrant,
tags: payload.tags,
updatedOn: firebase.firestore.FieldValue.serverTimestamp()
})
.then(() => {
commit('ADD_NEW_TASK', payload)
})
.catch(error => {
commit('setError', error.message)
})
},
getTasks ({ commit }) {
// commit('loadedTasks', false)
db.collection('tasks').orderBy('createdOn', 'desc').get().then(querySnapshot => {
querySnapshot.forEach(doc => {
const data = {
'taskId': doc.id,
'title': doc.data().title,
'quadrant': doc.data().quadrant
}
this.state.tasks.push(data)
})
// commit('loadedTasks', true)
commit('GET_TASKS', this.state.tasks)
})
},
deleteTask ({ commit }, payload) {
db.collection('tasks').doc(payload).delete().then(() => {
commit('DELETE_TASK', payload)
}).catch(function (error) {
// console.error('Error removing document: ', error)
commit('setError', error.message)
})
}
},
// Getters: receive data from the store
getters: {
isAuthenticated (state) {
return state.user !== null && state.user !== undefined
}
}
})
TaskList:
<template>
<ul>
<li>
<h4>{{title}}</h4>
</li>
<li v-for="task in tasks" :key="task.taskId" v-if="task.quadrant === Id" class="collection-item">
<task-list-item v-bind:task="task"></task-list-item>
</li>
</ul>
</template>
<script>
import TaskListItem from './TaskListItem'
export default {
components: {
'task-list-item': TaskListItem
},
data () {
return {
}
},
props: ['Id', 'title'],
computed: {
tasks () {
return this.$store.state.tasks
}
},
methods: {
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
TaskListItem:
<template>
<div>{{task.title}} ({{task.taskId}})<v-spacer></v-spacer>
<v-scroll-x-transition>
<v-btn flat icon color="primary" v-show="!deleteConfirmation">
<v-icon>edit</v-icon>
</v-btn>
</v-scroll-x-transition>
<v-scroll-x-transition>
<v-btn flat icon color="primary" v-show="!deleteConfirmation" @click="deleteConfirmation = !deleteConfirmation">
<v-icon>delete</v-icon>
</v-btn>
</v-scroll-x-transition>
<v-scroll-x-transition>
<v-btn flat icon color="green" v-show="deleteConfirmation" @click="deleteConfirmation = !deleteConfirmation">
<v-icon>cancel</v-icon>
</v-btn>
</v-scroll-x-transition>
<v-scroll-x-transition>
<v-btn flat icon color="red" v-show="deleteConfirmation" @click="deleteConfirmation = !deleteConfirmation; deleteTask(task.taskId)">
<v-icon>check_circle</v-icon>
</v-btn>
</v-scroll-x-transition>
</li>
</div>
</template>
<script>
export default {
data () {
return {
deleteConfirmation: false
}
},
props: ['task'],
computed: {
},
methods: {
deleteTask (taskId) {
this.$store.dispatch('deleteTask', taskId)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
Upvotes: 0
Reputation: 7033
You have no mutation to actually change your tasks
array, as e. g. this.state.tasks.pop(payload)
happens in the deleteTask
action, not the mutation.
The only way to actually change state in a Vuex store is by committing a mutation. https://vuex.vuejs.org/guide/mutations.html
That's also the reason why you see right result after reloading: The Firestore implementation is fine and updates its values. It will show the updated and newly fetched tasks
array after reloading, but the Vuex state never gets changed before reloading, so Vue's displaying of the tasks
array stays the same.
The solution to this is pretty straightforward:
Create a new mutation:
REMOVE_FROM_TASKS (state, payload) {
// algorithm to remove, e. g. by id
state.tasks = state.tasks.filter(e => {
return e.id !== payload;
});
},
Use this mutation in your action:
deleteTask ({ commit }, payload) {
db.collection('tasks').doc(payload).delete().then(() => {
commit('REMOVE_FROM_TASKS', payload)
commit('deletedTask', true)
commit('loadedTasks', true)
}).catch(function (error) {
console.error('Error removing document: ', error)
commit('deletedTask', false)
})
}
Upvotes: 2