I am building a simple Vue.js todo list app using Vue.js, Vuex, and Firebase. The Vuex store dispatches, commits, and returns the inputted data just as it should, but I want to be able to connect the app to a Firestore database. So far, I have managed to set up the app so that data is pushed into the collection, but I also want the database to return a snapshot of the firestore data to the DOM, as well as to enable deleting of data from database. I have experience with these Firestore methods in simple non Vuex-projects, but am not sure how to synthesize Firestore methods with a Vuex store. How can I do this? Here is what I have so far. Thanks so much!
<!--GetTodo.vue-->
<template>
<div id="get-todo" class="container">
<input class="form-control" :value="newTodo" #change="getTodo" placeholder="I need to...">
<button class="btn btn-primary" #click="addTodo">Add New Post</button>
<ul class="list-group">
<li class="list-group-item" v-for="todo in todos">
{{todo.body}}
<div class="btn-group">
<button type="button" #click="remove(todo)" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-remove-circle"></span> Remove
</button>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
methods: {
getTodo(e) {
this.$store.dispatch('getTodo', e.target.value)
},
addTodo() {
this.$store.dispatch('addTodo')
this.$store.dispatch('clearTodo')
},
remove(todo){
this.$store.dispatch('removeTodo', todo)
}
},
computed: {
todos(){
return this.$store.getters.todos
},
newTodo() {
return this.$store.getters.newTodo
}
}
}
</script>
<style>
</style>
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
import db from '../firebase';
export default new Vuex.Store({
state: {
todos: [],
newTodo: ''
},
mutations: { //syncronous, committed
GET_TODO(state, todo){
state.newTodo = todo
},
ADD_TODO(state){
state.todos.push({
body: state.newTodo,
completed: false
})
db.collection('messages').add({
content: state.newTodo
})
},
REMOVE_TODO(state, todo){
var todos = state.todos
todos.splice(todos.indexOf(todo), 1)
},
CLEAR_TODO(state){
state.newTodo = ''
}
},
actions: { //asyncronous, dispatched
getTodo({commit}, todo){
commit('GET_TODO', todo)
},
addTodo({commit}){
commit('ADD_TODO')
},
removeTodo({commit}, todo){
commit('REMOVE_TODO', todo)
},
clearTodo({commit}){
commit('CLEAR_TODO')
}
},
getters: {
newTodo: state => state.newTodo,
todos: state => state.todos.filter((todo) => {
return !todo.completed
})
}
})
<!--App.vue-->
<template>
<div id="app" class="container">
<GetTodo></GetTodo>
</div>
</template>
<script>
import GetTodo from './components/GetTodo.vue'
export default {
components: {
GetTodo
}
}
</script>
<style>
body {
font-family: Helvetica, sans-serif;
}
li {
margin: 10px;
}
</style>
You can make sync in your mutation, see the example below:
source: https://www.codewall.co.uk/how-to-create-a-real-time-to-do-list-app-with-vue-vuex-firebase-tutorial/
import Vue from 'vue'
import Vuex from 'vuex'
import { db } from '#/main'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
items: null
},
getters: {
getItems: state => {
return state.items
}
},
mutations: {
setItems: state => {
let items = []
db.collection('items').orderBy('created_at').onSnapshot((snapshot) => {
items = []
snapshot.forEach((doc) => {
items.push({ id: doc.id, title: doc.data().title })
})
state.items = items
})
}
},
actions: {
setItems: context => {
context.commit('setItems')
}
}
})
import { db } from '#/main'
export default {
name: 'home',
beforeCreate: function () {
this.$store.dispatch('setItems')
},
data: function () {
return {
myTodo: '',
errors: ''
}
},
Related
I have created a Vue3 to-do list project with VueCLI(VueX) for practice. I can add items to the array of objects and display them from objects.
Now, I want to implement a delete function that when I click the delete button beside the item, it deletes the element and also removes the object from array.
Here is my code:
NoteInput.vue
<template>
<div>
<input
type="text"
v-model="inputValue"
#keyup.enter="addItem"
/>
</div>
</template>
<script>
import { ref } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const inputValue = ref()
const store = useStore()
const addItem = () => {
if (inputValue.value !== '') {
store.commit('addItem', inputValue.value)
}
inputValue.value = ''
}
return {
inputValue,
addItem
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>
NoteItem.vue
<template>
<div>
<div
v-for="(item, index) in list"
:key="index"
>
<span>{{item.title}}</span>
<span>
<button #click="deleteItem">Delete</button>
</span>
</div>
</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const list = store.state.list
const deleteItem = () => {
// store.commit('deleteItem', this.item.title)
console.log()
}
return {
list,
deleteItem
}
}
}
</script>
store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
list: []
},
getters: {
},
mutations: {
addItem(state, item) {
state.list.push({
title: item,
status: 'normal'
})
},
deleteItem(state, item) {
}
},
actions: {
},
modules: {
}
})
Please modify your NoteItem.vue and store/index.js files as below.
Working codesandbox link https://codesandbox.io/s/vue-3-vuex-4-vue-router-forked-ei4x1r
NoteItem.vue
<template>
<div>
<div
v-for="(item, index) in list"
:key="index"
>
<span>{{item.title}}</span>
<span>
<button #click="deleteItem(index)">Delete</button>
</span>
</div>
</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const list = store.state.list
const deleteItem = () => {
store.commit('deleteItem', index)
}
return {
list,
deleteItem
}
}
}
</script>
store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
list: []
},
getters: {
},
mutations: {
addItem(state, item) {
state.list.push({
title: item,
status: 'normal'
})
},
deleteItem(state, index) {
state = state.list.splice(index, 1);
}
},
actions: {
},
modules: {
}
})
I have difficult to use vuex global state combine with re-render child-component in Vue.js.
The global state is mutated but does not re-render its data in v-for loop.
All list of data is rendered, but when the new data changes, component in /blog does not change data in it.
Here is some code:
/store/index.js
export const state = () => ({
allData: [],
})
export const getters = {
getAllData: (state) => state.allData,
}
export const mutations = {
GET_DATAS(state, payload) {
state.allData = payload
},
UPDATE_DATA(state, payload) {
const item = state.allData[payload.index]
Object.assign(item, payload)
},
}
export const actions = {
getDatas({ commit, state }, payload) {
return fetch(`URL_FETCH`)
.then((data) => data.json())
.then((data) => {
commit('GET_DATAS', data)
})
.catch((err) => console.log(err))
},
updateData({ commit, state }, payload) {
commit('UPDATE_DATA', payload)
},
}
in /layouts/default.vue
beforeCreate() {
this.$store.dispatch('getDatas').then(() => {
connectSocket()
})
},
methods: {
connectSocket() {
// connect & received message from socket
// received message from socket
this.$root.$emit('updateData', {
index: 12,
price: 34,
change: 56,
percent: 78,
})
},
},
and in /pages/blog/index.vue
<template>
<div>
<div
v-for="index in getAllData"
:key="index.name"
class="w-100 grid-wrapper"
>
<div>{{ index.price }}</div>
<div>{{ index.change }}</div>
<div>{{ index.percent }}</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapGetters(['getAllData']),
},
mounted() {
this.$root.$on('updateData', (item) => {
this.$store.dispatch('updateData', {
index: item.index,
price: item.price,
percent: item.percent,
change: item.change,
})
})
},
}
</script>
Here is a complete example on how to use Vuex and load the data efficiently into a Nuxt app (subjective but using good practices).
/pages/index.vue
<template>
<div>
<main v-if="!$fetchState.pending">
<div v-for="user in allData" :key="user.id" style="padding: 0.5rem 0">
<span>{{ user.email }}</span>
</div>
</main>
<button #click="fakeUpdate">Update the 2nd user</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data() {
return {
mockedData: {
name: 'John Doe',
username: 'jodoe',
email: 'yoloswag#gmail.com',
phone: '1-770-736-8031 x56442',
website: 'hildegard.org',
},
}
},
async fetch() {
await this.setAllData()
},
computed: {
...mapState(['allData']),
},
methods: {
...mapActions(['setAllData', 'updateData']),
fakeUpdate() {
this.updateData({ index: 1, payload: this.mockedData })
},
},
}
</script>
/store/index.js
import Vue from 'vue'
export const state = () => ({
allData: [],
})
export const mutations = {
SET_ALL_DATA(state, payload) {
state.allData = payload
},
UPDATE_SPECIFIC_DATA(state, { index, payload }) {
Vue.set(state.allData, index, payload)
},
}
export const actions = {
async setAllData({ commit }) {
try {
const httpCall = await fetch('https://jsonplaceholder.typicode.com/users')
const response = await httpCall.json()
commit('SET_ALL_DATA', response)
} catch (e) {
console.warn('error >>', e)
}
},
updateData({ commit }, { index, payload }) {
commit('UPDATE_SPECIFIC_DATA', { index, payload })
},
}
I have this app to make I am dealing with the CRUD and only the "U(pdate)" is posing problems
I have :
<template>
<EditQuestion
v-show="showEditQ"
:questToEdit="questToEdit"
/>
</template>
<script>
import EditQuestion from '../components/EditQuestion'
export default {
name: 'Home',
components: {
EditQuestion
},
data() {
return {
questToEdit:{},
}
},
methods:{
async getQuestion(_id){
const questToEdit = await this.fetchQuestion(_id)
return questToEdit
},
async fetchQuestion(_id) {
const res = await fetch(`http://localhost:3000/api/questions/${_id}`)
const data = await res.json()
return data
},
}
</script>
and in the component side I have
<template>
<div>
<p>test</p>
<p>{{questToEdit.question}}</p>
<li
v-for="(proposition,index) in questToEdit.propositions"
:key="index"
>{{proposition.props}}
</li>
</div>
</template>
<script>
export default {
name: 'EditQuestion',
props: {
questToEdit:Object
},
data(){
return {
}
},
}
</script>
I feel like I can't access questToEdit in the component (it's doing nothing ) or its never called on home ? thx for your time in advence.
When writing an application I get the same error as in the title. I create an application in Vue and store the data in the firestore.
The data is sent via this.$store.dispatch ('function'). I get an error in this file on line 47 EditNote.vue
methods: {
editNote() {
this.$store.dispatch('editNote', this.docId, {
content: this.note.content
});
this.note.content = '';
}
})
and I have no idea what it is, maybe some of you can help me. Thank you if any of you could help. 🤞
./src/components/EditNote.vue
<template>
<div class="editnote">
<div class="content">
<div class="content__header">
<h2 class="content__title">
Edytuj notatkę 🐝
</h2>
<span class="content__close" #click="$emit('close')">
<i class="fas fa-times"></i>
</span>
</div>
<div class="content__inside">
<form #submit.prevent>
<textarea
class="content__textarea"
v-model.trim="note.content"
rows="6"
cols="20"
:placeholder="this.noteContent"
></textarea>
<span class="content__plus-icon" #click="editNote"
><i class="fas fa-edit"></i
></span>
</form>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
props: ['docId', 'noteContent'],
data() {
return {
note: {
content: ''
}
};
},
methods: {
editNote() {
this.$store.dispatch('editNote', this.docId, {
content: this.note.content
});
this.note.content = '';
}
}
};
</script>
./src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import * as fb from '../firebase';
import router from '../router/index';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
userProfile: {},
notes: []
},
mutations: {
setUserProfile(state, val) {
state.userProfile = val;
},
setNotes(state, val) {
state.notes = val;
}
},
actions: {
async getNotes() {
await fb.usersCollection
.doc(fb.auth.currentUser.uid)
.collection('notes')
.orderBy('createdOn', 'desc')
.onSnapshot(snapshot => {
let notesArray = [];
snapshot.forEach(doc => {
let note = doc.data();
note.id = doc.id;
notesArray.push(note);
});
store.commit('setNotes', notesArray);
});
},
async addNote({}, note) {
await fb.usersCollection
.doc(fb.auth.currentUser.uid)
.collection('notes')
.add({
createdOn: new Date(),
content: note.content,
userId: fb.auth.currentUser.uid
});
},
async editNote({ commit }, docId, note) {
await fb.usersCollection
.doc(fb.auth.currentUser.uid)
.collection('notes')
.doc(docId)
.update({
createdOn: new Date(),
content: note.content
});
},
async deleteNote({}, docId) {
if (window.confirm('Jesteś pewny/a, że chcesz usunąć notatkę?')) {
await fb.usersCollection
.doc(fb.auth.currentUser.uid)
.collection('notes')
.doc(docId)
.delete();
}
},
async signup({ dispatch }, form) {
// sign user up
const { user } = await fb.auth.createUserWithEmailAndPassword(
form.email,
form.password
);
// create user profile object in userCollection
await fb.usersCollection.doc(user.uid).set({
username: form.username,
email: form.email,
password: form.password
});
// fetch user profile and set in state
dispatch('fetchUserProfile', user);
router.push('/login');
},
async login({ dispatch }, form) {
// sign user in
const { user } = await fb.auth.signInWithEmailAndPassword(
form.email,
form.password
);
// fetch user profile and set in state
dispatch('fetchUserProfile', user);
},
async logout({ commit }) {
await fb.auth.signOut();
// clear userProfile and redirect to /login
commit('setUserProfile', {});
router.push('/login');
},
async fetchUserProfile({ commit }, user) {
// fetch user profile
const userProfile = await fb.usersCollection.doc(user.uid).get();
// set user profile in state
commit('setUserProfile', userProfile.data());
// change router to dashboard
if (router.currentRoute.path === '/login') {
router.push('/');
}
}
},
modules: {}
});
export default store;
./src/views/Homepage.vue
<transition
enter-active-class="animate__animated animate__backInUp animate__faster"
leave-active-class="animate__animated animate__backOutDown animate__faster"
mode="out-in"
appear
>
<EditNote
v-if="showEditNote"
#close="toggleEditNote()"
:docId="selectedNote"
:noteContent="selectedNoteContent"
></EditNote>
</transition>
<div class="notes">
<div
class="notes__container-title-icon"
#click="toggleAddNotes"
>
<h3 class="notes__title">Notatki</h3>
<span class="notes__plus-icon">
<i class="fas fa-plus"></i>
</span>
</div>
<ul class="notes__list" v-if="notes.length">
<li
class="notes__item"
v-for="note in notes"
:key="note.id"
>
{{ note.content }}
<br />
<span class="notes__createdOn">{{
getCurrentDate(note.createdOn)
}}</span>
<br />
<div class="notes__extras">
<span
class="notes__edit"
#click="toggleEditNote(note.id, note.content)"
><i class="far fa-edit"></i
></span>
<span
class="notes__delete"
#click="deleteNote(note.id)"
><i class="far fa-trash-alt"></i
></span>
</div>
</li>
</ul>
<ul class="notes__list" style="list-style-type: none" v-else>
<li class="notes__item">
Nie ma żadnej notatki 😢
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import AddNotes from '#/components/AddNotes';
import EditNote from '#/components/EditNote';
import { mapState } from 'vuex';
export default {
data() {
return {
showAddNotes: false,
showEditNote: false,
selectedNote: '',
selectedNoteContent: '',
};
},
components: {
AddNotes,
EditNote
},
computed: {
...mapState(['userProfile', 'notes']),
},
beforeMount() {
this.getCurrentDate();
},
created() {
this.getNotes();
},
methods: {
toggleEditNote(docId, noteContent) {
this.showEditNote = !this.showEditNote;
if (this.showEditNote) {
this.selectedNote = docId;
this.selectedNoteContent = noteContent;
} else {
this.selectedNote = {};
this.selectedNoteContent = {};
}
},
toggleAddNotes() {
this.showAddNotes = !this.showAddNotes;
},
getNotes() {
this.$store.dispatch('getNotes');
},
deleteNote(docId) {
this.$store.dispatch('deleteNote', docId);
},
The issue is with the editNote action. Actions can only receive one argument so you'll need to pass multiple values as an object or array. Change it to:
async editNote({ commit }, { docId, note }) { // Notice the braces
await fb.usersCollection
.doc(fb.auth.currentUser.uid)
.collection('notes')
.doc(docId)
.update({
createdOn: new Date(),
content: note.content
});
},
And call it in your component this way:
methods: {
editNote() {
const docId = this.docId;
const note = {
content: this.note.content
};
this.$store.dispatch('editNote', { docId, note }); // Notice the braces
this.note.content = '';
}
}
I've an Problem with the vuex-Store. Theres one state in my store which is not be updated, when the Action is called. Maybe anyone can support me here? The problem is the state of "selectedHive". The axios-call is working well and get the correct response. But the Object would not be updated in store.
Here are the involved files:
Store:
import merge from 'vuex'
import axios from 'axios'
export const state = () => ({
selectedHive: {},
hivesList: []
})
export const mutations = {
set(state, hives) {
state.hivesList = hives
},
add(state, value) {
merge(state.hivesList, value)
},
remove(state, { hive }) {
state.hivesList.splice(state.hivesList.indexOf(hive), 1)
},
setHive(state, hive) {
state.selectedHive = hive
console.table(state.selectedHive)
}
}
export const actions = {
async get({ commit }) {
await this.$axios.get('http://localhost:8080/api/v1/hives').then((res) => {
if (res.status === 200) {
commit('set', res.data)
}
})
},
async show({ commit }, params) {
await this.$axios
.get(`http://localhost:8080/api/v1/hives/${params.id}`)
.then((res) => {
if (res.status === 200) {
console.log('ID: ' + params.id)
commit('setHive', res.data)
}
})
},
async set({ commit }, hive) {
await commit('set', hive)
},
async getHive({ commit }, params) {
console.log('getHive called' + params)
return await axios
.get(`http://localhost:8080/api/v1/hives/${params}`)
.then((res) => {
console.log(res.data)
console.log(typeof res.data)
commit('setHive', res.data)
})
.catch((err) => {
console.log(err)
})
}
}
Component:
<template>
<div class="div-box">H: {{ selectedHive }}</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
props: {
hiveid: {
type: String,
required: true
}
},
async fetch({ store }) {
this.getHive(this.hiveid)
console.log('Passing: ' + this.hiveid)
await store.dispatch('hives/getHive', this.hiveid)
},
computed: {
...mapState({
selectedHive: (state) => state.hive.selectedHive
})
},
created() {
console.log('id: ' + this.hiveid)
this.getHive(this.hiveid)
},
methods: {
...mapActions('hives', ['getHive'])
}
}
</script>
<style scoped>
.div-box {
/* width: 49%; */
border: 1px solid black;
padding: 10px;
}
</style>
parent page:
<template>
<div>
<h1>Locations</h1>
<!-- <div>LOCATIONS liste: {{ locationList }}<br /><br /></div>
<div>Selected LOCATION: {{ selectedLocation }}<br /><br /></div> -->
<div v-for="loc in locationList" :key="loc.id">
<div class="div-box">
u-Id: {{ loc._id }} <br />Name: {{ loc.name }} <br />
Adresse: {{ loc.adress }} <br />
Koordinaten: {{ loc.longitude }} , {{ loc.latitude }} Völker: <br />
<div v-for="hive in loc.hives" :key="hive._id">
{{ hive._id }}
<hiveIcon :hiveid="hive.hiveId" />
</div>
</div>
<br /><br />
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import hiveIcon from '#/components/hiveIcon'
export default {
components: {
hiveIcon
},
computed: {
...mapState({
locationList: (state) => state.locations.locationsList,
selectedLocation: (state) => state.locations.selectedLocation,
hivesList: (state) => state.hives.hivesList,
selectedHive: (state) => state.hives.selectedHive
})
}
}
</script>
<style scoped>
.div-box {
/* width: 49%; */
border: 1px solid black;
padding: 10px;
}
</style>
I would guess, that it's something related to your state structure and how you access it.
You have
export const state = () => ({
selectedHive: {},
hivesList: []
})
in your state, but when mapping you access hive before selectedHive:
...mapState({
selectedHive: (state) => state.hive.selectedHive
})
Try to access it directly, like: selectedHive: (state) => state.selectedHive
EDIT:
Could you try to setup a watcher on that selectedHive?
watch: {
selectedHive: {
deep: true,
handler() {
console.log('selectedHive has changed');
}
}
}