How to load firestore collection to Vuex State? - javascript

My goal is to load a firestore collection into the Vuex state and use it for multiple pages. I was trying to follow :
How to get collection from firestore and set to vuex state when the app is rendered?
I followed this post and either I am missing something or possibly the code is outdated? I am very new to Vuex so I could be doing something wrong but I am not sure.
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
const db = require('../components/fbInit')
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
categories: []
},
actions: {
fetchCategories({ commit }) {
db.collection('one').get().then(querySnapshot => {
if (querySnapshot.empty) {
// eslint-disable-next-line no-console
console.log('cannot find')
//this.$router.push('/HelloWorld')
} else {
this.loading = false;
var categories = [];
querySnapshot.forEach(doc => {
categories.push(doc.data());
});
commit("setCategories", categories);
}
});
}
},
mutations: {
setCategories(state, val) {
state.categories = val;
}
}
});
store.dispatch("fetchCategories");
export default store;
One.vue
<template>
<div class="flex-row justify-center ma-12">
<ul>
<li v-for="category in categories" :key="category.name">{{category.name}}</li>
</ul>
</div>
</template>
<script>
import { mapActions } from "vuex";
import { mapGetters } from "vuex";
// eslint-disable-next-line no-unused-vars
export default {
computed: {
categories() {
return this.$store.state.categories;
},
...mapGetters([])
},
methods: {
...mapActions(["fetchCatagories"])
}
};
</script>
I tested to see if the firestore is connected and it displayed its contents. But I am getting this error: Uncaught TypeError: db.collection is not a function.
I have never been able to load my firestore collection into the Vuex store state and load it. Again I am new to using vuex so any help will be greatly appreciated
TLDR; Goal: Load firestore collection('One'), Store it in Vuex state, Use Vuex store to load the same data on multiple pages without having to call the data on every page.

store/index.js
const store = new Vuex.Store({
....
getters: {
categories (state) {
return state.categories;
}
},
....
});
One.vue
export default {
....
computed: {
...mapGetters(['categories'])
},
....
}

const db = require('../components/fbInit') might be your problem here. In a firestore/vue project I own, my db is declared as:
import firebase from '../firebase';
const db = firebase.firestore();

Related

Vuex 4, State is empty in component

I am trying to access subjects store state using this.$store.state.subjects inside my home component however it comes up as an empty array. Using console.log the only place I am able to see the state.subjects populated is if its in the mutation function.
Anywhere else the console.log is empty. It seems to me that the state is not persisting from the mutations, but I'm not sure why.
I have tried quite a few stackoverflow answers however, non of them fix the issues or I have no clue what I am reading in the post. I have also left of code from my code blocks to make this post more readable, such as imports or templates.
Store index.js
export default createStore({
state: {
subjects: [],
},
actions: {
getSubjects({ commit }) {
// Manages subjects, allow for display in column or Calendar view
axiosMain({
method: "get",
url: "/study/",
withCredentials: true,
})
.then((response) => {
commit("setSubjects", response.data);
})
},
},
mutations: {
setSubjects(state, subjectsToSet) {
state.subjects = subjectsToSet;
console.log(state.subjects) # Is a populated array
}
}
});
Main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import VueGtag from "vue-gtag-next";
import store from "./store";
import "./assets/main.css";
createApp(App)
.use(router)
.use(store)
.use(VueGtag, {
property: {
id: "G-E4DPXQ96HB",
},
})
.mount("#app");
Home.vue
<template>
</template>
<script>
export default {
name: "Home",
data() {
return {
subjects: [],
};
},
mounted() {
this.callStoreSubjectAction();
this.setSubjectsToStoreSubject();
},
methods: {
callStoreSubjectAction() {
this.$store.dispatch("getSubjects");
},
setSubjectsToStoreSubject() {
this.subjects = this.$store.state.subjects;
console.log(this.$store.state.subjects); # Is an empty array
},
},
};
</script>
In the component, you're copying the value of this.$store.state.subjects before the axios call has completed. Wait for the promise to resolve first. To do that, you'll need to first return the promise from the action:
getSubjects({ commit }) {
return axiosMain({ // returning the promise
...
}
}
Waiting for the promise:
mounted() {
this.$store.dispatch("getSubjects").then(r => {
this.subjects = this.$store.state.subjects;
console.log(this.$store.state.subjects);
});
},
Better than this would be to remove subjects from your component data and use a computed instead to sync with the Vuex state:
import { mapState } from 'vuex';
computed: {
...mapState(['subjects']) // creates a computed `this.subjects`
}
Then you would only have to dispatch the action and the component will take care of the rest:
mounted() {
this.$store.dispatch("getSubjects");
}

Returning data from store.js in vuejs returns TypeError

I have a store.js file which sends an api request and gets responses using axios,
the api is tested and working perfectly.
store.js contains this code :
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
import cUser from './modules/User';
import UI from './modules/UI';
Vue.use(Vuex)
export default new Vuex.Store({
data: function () {
return {
responsedata: []
};
},
viewresults: async (commit, payload)=>{
let token =localStorage.getItem('token');
let username= payload.username;
await axios.post('search',{token, username}).then(response => {
this.responsdata = response.data;
}).catch(er=>{
console.log(er);
});
and i have this function in other file that uses it :
search(){
console.log('search clicked');
console.log(this.username);
this.responsedata = this.$store.dispatch('viewresults',{
username: this.username,
});
console.log(this.responsedata);
},
}
but i get this error in the browser console :
TypeError: Cannot set property 'responsedata' of undefined
it seems like that viewresult in the store.js can't see the responsedata variable defined in data return function .
Let me show you an example about how to use the Vuex store:
// main.js
import Vue from 'vue';
import App from './App.vue';
import store from '#/store/index';
new Vue({
el: '#app',
store,
components: {
App
},
render: h => h(App)
});
Now the store.js file:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios'
Vue.use(Vuex);
const state = {
responseData: []
}
const mutations = {
setResponse(state, data) {
state.responseData = data;
}
}
const actions = {
async viewResults({ commit }, payload) {
let token =localStorage.getItem('token');
let username= payload.username;
await axios.post('search', { token, username }).then(response => {
commit('setResponse', response.data);
}).catch(er=>{
console.log(er);
});
}
}
export const store = new Vuex.Store({
state,
mutations,
actions
});
And a component to call the action and show the information:
// SearchAndShowData.vue component
<template>
<div>
<button click="search">
Search
</button>
{{ responseData}}
</div>
</template>
<script>
import {mapActions, mapState} from 'vuex';
export default {
name: "SearchAndShowData",
data: () => ({
username: "Andres",
}),
computed() {
// Expose the state.responseData object as responseData
...mapState(['responseData']) // If you want to preprocess the data, use create a getter and use mapGetters
// As a computed property, it will be updated always that responseData has any change
},
methods: {
...mapActions(["viewResults"]), // Expose the viewResults action as a method, call it with this.viewResults
search() {
this.viewResults({username: this.username })
}
}
}
</script>
I didn't test it, but this is the idea behind a vuex store and how to use it, if somebody sees an error, please let me know to update the answer (thanks).
Hope you can update your code with this information and it can work properly.
Also, Scrimba has a free course that could help you to extend your knowledge about Vuex, check it here

Use fetch method inside vuex action

I created a store action that fetch api. When I'm trying to dispatch it from component in created lifecycle hook. I'm getting Cannot read property 'dispatch' of undefined error. I know there are several similar questions but none of them solved this issue.
I tried to dispatch it also in normal method and still get this error.
store.js
import Vue from "vue";
import Vuex from "Vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
categories: []
},
mutations: {
SET_CATEGORIES(state, categories) {
state.categories = categories;
}
},
actions: {
getCategories({ commit }) {
return fetch("https://api.chucknorris.io/jokes/categories")
.then(response => {
return response.json();
})
.then(jsonObj => {
commit("SET_CATEGORIES", jsonObj);
})
.catch(error => {
console.log(error);
});
}
}
});
And this is the component which I try to dispatch in -
<script>
export default {
data() {
return {
joke: "",
categories: [],
selectedCat: ""
};
},
computed: {
disabled() {
if (this.joke) {
return false;
} else {
return true;
}
}
},
methods: {
addToFavs: function() {
this.$emit("pushJoke", this.joke);
this.fetchJoke();
}
},
created() {
this.$store.dispatch('getCategories');
}
};
</script>
What am I doing wrong?
Start by adding
import { mapActions } from 'vuex'
in your script
then add to methods
methods: {
...mapActions([
'getCategories'
])
.
.
}
and alter your created method to
created() {
this.getCategories()
}
Furthermore, you might want to create a vuex action to replace that this.$emit("pushJoke", this.joke); line that you have, and map that in a similar way that you have mapped getCategories
That's because you missed to add your vuex store option to the root instance of Vue. To solve that import your store and attach it to your vue instance.
import { store } from '.store'
const app = new Vue({
store
})

Persistent state using vuex-persistedstate: store state in first tab doesnot update when i mutate state in another tab

Hi I am using vuex persistedstate to persist my state.
import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
// ...
plugins: [createPersistedState()]
})
What I want to do is update a store state in another tab and the updated store state should be reflected in other open tabs of my app.
How can I achieve so.
Use for it vuex-shared-mutations:
GitHub link
You can try implementing a window.onfocus event so that when that you go to the new tab, the getState method gets called.
You can listen to the storage event. See Responding to storage changes with the StorageEvent in https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API .
window.addEventListener("storage", e => {
// ...
});
Example: https://github.com/jacobgoh101/vuex-sync-tabs-example
in store.js
import Vue from "vue";
import Vuex from "vuex";
import createPersistedState from "vuex-persistedstate";
Vue.use(Vuex);
export default new Vuex.Store({
plugins: [createPersistedState()],
state: {
count: 2
},
mutations: {
increment(state) {
state.count++;
},
setAll(state, payload) {
state.count = payload.count;
}
},
actions: {}
});
in App.vue
<template>
<div id="app">
store count: {{$store.state.count}}
<br>
<button #click="$store.commit('increment')">add</button>
</div>
</template>
<script>
export default {
name: "app",
mounted() {
window.addEventListener("storage", e => {
if (e.key !== "vuex") return;
// exit if no change
if (e.newValue === JSON.stringify(this.$store.state)) return;
const persistedData = JSON.parse(e.newValue);
this.$store.commit("setAll", persistedData);
});
}
};
</script>

Property or method not defined on the instance but referenced during render I Vuex

I'm getting the following error.
[Vue warn]: Property or method "updateData" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
As far I can tell by the code, the method is there, so I'm stuck on something that I miss due to my ignorance of Vuex. I've googled the matter and got quite a few answers but none of them made me any wiser what to do. It seems to be something with scope, I'm sensing.
I also get the error below but I suspect that it's the same root cause for both so solving the one will resolve the other.
[Vue warn]: Invalid handler for event "click": got undefined
(found in component at ...)
The markup is as follow. I've checked that the path goes to the right location. At the moment I'm not sure at all how to even start to troubleshoot it. Any hints would be appreciated.
<template>
<div id="nav-bar">
<ul>
<li #click="updateData">Update</li>
<li #click="resetData">Reset</li>
</ul>
</div>
</template>
<script>
import { updateData, resetData } from "../vuex_app/actions";
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData, resetData }
}
}
</script>
Edit
After input I improved the export to include methods property like so. (Still the same error remaining, though.)
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData, resetData },
methods:{
updateData: () => this.$store.dispatch("updateData"),
resetData: () => this.$store.dispatch("resetData")
}
}
}
Do I have to do something extra in the store? It looks like this.
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = { dataRows: [], activeDataRow: {} };
const mutations = {
UPDATE_DATA(state, data) { state.dataRows = data; state.activeDataRow = {}; },
RESET_DATA(state) { state.dataRows = []; state.activeDataRow = {}; }
};
export default new Vuex.Store({ state, mutations });
You have to add the imported functions in the methods of Vue component, like following. You can take help of mapActions as explained in the documentation. This is needed to map this.updateDate to this.$store.dispatch('updateDate').
<script>
import { updateData, resetData } from "../vuex_app/actions";
import { mapActions } from 'vuex'
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData, resetData }
},
methods: {
...mapActions(['updateData', 'resetData'])
}
}
</script>
Edited
In case you dont want to use mapActions, you can use this.$store.dispatch as you are using in your example, however you need to have methods at vue compoenent level (documentation) and not insise vuex, as following:
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData, resetData }
},
methods:{
updateData: () => this.$store.dispatch("updateData"),
resetData: () => this.$store.dispatch("resetData")
}
}

Categories