vuex store token doesn't update accross components - vue.js

I have the following store/index.js file:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
token: null
}
const mutations = {
setToken (state, token) {
state.token = token
localStorage.setItem('token', token)
},
removeToken (state) {
state.token = null
localStorage.removeItem('token')
}
}
const getters = {
isLoggedIn: state => {
return state.token != null
}
}
export default new Vuex.Store({
state,
getters,
mutations
})
and in my main.js I add it as such:
import store from './store'
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
and when I login I set the token as such:
this.$store.commit('setToken', response.data.token)
Which should be working because the setToken function successfully save it to localStorage.
However, if I go to my other components and call:
data () {
return {
loggedIn: false
}
},
mounted: function () {
this.loggedIn = this.$store.getters.isLoggedIn
console.log(this.loggedIn)
}
then console will print 'false'. I have also tried to change 'mounted' to 'computed', 'created' and 'ready' but the result is the same.
I can only seem to solve the problem if I do like this:
mounted: function () {
this.token = localStorage.getItem('token')
this.$store.commit('setToken', this.token)
},
computed: {
isLoggedIn () {
return this.$store.getters.isLoggedIn
}
}
for which isLoggedIn will finally be true.
But if I do like that then what's the point of vuex..
I'm thinking I either do something wrong with not knowing whether to call computed/mounted/etc, or maybe the way I do mutations is wrong, or maybe I don't initialize Vuex in the proper file.. but I feel like I've tried it all now

Your component is probably mounted before you login and set the token.
Seeing as you're using local storage for token persistence, I would initialise the value that way, ie
const state = {
token: localStorage.getItem('token')
}
This will be null if it has not been set (or has been removed).

Related

Mocking just part of getters when importing the global store

Any idea if it is possible to mock just a getter from the global store ?
I tried this code but It does not work:
import store from '#/store';
import Vuex from "vuex";
const localVue = createLocalVue();
localVue.use(VueRouter);
localVue.use(Vuex);
const wrapper = mount(App, {
mocks: {
$store: {
getters: {
isLoggedIn: () => true // This is always returning false. I need this getter to return true value
},
}
},
store, // this is the global store for my application
localVue,
router,
});
It would be much easier just to use mocks property while mounting the component without calling localVue.use(Vuex) and without creating store instance:
const wrapper = mount(App, {
localVue,
router,
mocks: {
$store: {
getters: {
isLoggedIn: () => () => true,
}
}
}
});
I solved my problem with inspiration from the Vue Testing Handbook examples here.
import * as otherNameThanStore from '#/store'; // we need to change the name to anyone other than store
import Vuex from "vuex";
const localVue = createLocalVue();
localVue.use(VueRouter);
localVue.use(Vuex);
const store = new Vuex.Store( // attribute name must be store, otherwise typescript will throw this error: is not assignable to parameter of type 'FunctionalComponentMountOptions<Vue>'
{
state: {
...otherNameThanStore.default.state
},
getters: {
...otherNameThanStore.default.getters,
isLoggedIn: (state) => () => true,
},
}
)
const wrapper = mount(App, {
store,
localVue,
router,
});
Hope it helps other people :)

Nuxt store mapGetters property is undefined on beforeCreate() hook

I'm experiencing a bug in my nuxt application working with vuex. I'm trying to access a store getter using mapGetters helper but when I access to that property in beforeCreate() hook value is undefined.
store/user.js
import VuexPersistence from "vuex-persist";
export const plugins = [VuexPersistence];
export const state = () => ({
user: null,
});
export const getters = {
isLoggedIn(state) {
if (state && state.user) {
console.log("state.user", state.user);
}
return state.user !== null && state.user !== {};
},
};
mycomponent.vue
export default {
beforeCreate() {
const isLoggedIn = this.$store.getters["user/isLoggedIn"];
console.log("computed isLoggedIn", this.isLoggedIn);
console.log("isLoggedIn", isLoggedIn);
},
computed: {
...mapGetters(["user/isLoggedIn"]),
},
};
</script>
Here is the output result in browser console
The store is not available in the beforeCreate hook. You could move your code to the mounted() hook, but I would recommend placing it in a middleware for checking if the user is logged in.
middleware/auth-check.js
export default function ({ store }) {
const isLoggedIn = store.getters["user/isLoggedIn"];
// do something...
}
Then add to your page:
export default {
...
middleware: 'auth-check'
...
}

from same axios to different components, VueJS

Okay, I have two different components and each of those get Axios response. But I don't want to fetch data in each component separate. That's is not right, and it cause components run separate...
Updated 3
I did some changes on the code, but still having some problems. I am doing axios call with Vuex in Store.js and import it into my component. it's like below.
This is my store.js component;
import Vue from "vue";
import Vuex from "vuex";
var actions = _buildActions();
var modules = {};
var mutations = _buildMutations();
const state = {
storedData: []
};
Vue.use(Vuex);
const getters = {
storedData: function(state) {
return state.storedData;
}
};
function _buildActions() {
return {
fetchData({ commit }) {
axios
.get("/ajax")
.then(response => {
commit("SET_DATA", response.data);
})
.catch(error => {
commit("SET_ERROR", error);
});
}
};
}
function _buildMutations() {
return {
SET_DATA(state, payload) {
console.log("payload", payload);
const postData = payload.filter(post => post.userId == 1);
state.storedData = postData;
}
};
}
export default new Vuex.Store({
actions: actions,
modules: modules,
mutations: mutations,
state: state,
getters
});
Now importing it into Average component.
import store from './Store.js';
export default {
name:'average',
data(){
return{
avg:"",
storedData: [],
}
},
mounted () {
console.log(this.$store)
this.$store.dispatch('fetchDatas')
this.storedData = this.$store.dispatch('fetchData')
},
methods: {
avgArray: function (region) {
const sum = arr => arr.reduce((a,c) => (a += c),0);
const avg = arr => sum(arr) / arr.length;
return avg(region);
},
},
computed: {
mapGetters(["storedData"])
groupedPricesByRegion () {
return this.storedData.reduce((acc, obj) => {
var key = obj.region;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj.m2_price);
return acc;
}, {});
},
averagesByRegion () {
let arr = [];
Object.entries(this.groupedPricesByRegion)
.forEach(([key, value]) => {
arr.push({ [key]: Math.round(this.avgArray(value)) });
});
return arr;
},
}
}
I can see the data stored in the console. But there are errors too. I can't properly pass the data in myComponent
https://i.stack.imgur.com/J6mlV.png
if you don't want use vuex to distrib data maybe you can try eventBus, when you get data form the axios respose #emit the event and in another component #on this event
The issue is that
To resolve the error you're getting, Below are the steps.
Import your store file inside the file where your Vue Instance is initialized.
// Assuming your store file is at the same level
import store from './store';
Inside your, Add store object inside your Vue Instance
function initApp(appNode) {
new Vue({
el: appNode,
router: Router,
store // ES6 sytax
});
}
There you go, you can now access your store from any component.
UPDATE: For Second Error
Instead of changing data inside your component, change it inside mutation in the store because you do not want to write the same login in other components where the same method is used.
Hence,
computed: {
...mapGetters(["storedData"]),
anotherFunction() {
// Function implementation.
}
}
Inside your mutation set the data.
SET_DATA(state, payload) {
console.log("payload", payload);
state.storedData = payload;
}
Inside getters, you can perform what you were performing inside your computed properties.
storedData: function(state) {
const postData = state.storedData.filter(post => post.userId == 1);
return postData;
}
Vuex Official docs
Here is the working codesandbox
Hope this helps!

vuex store state update value in middleware check-auth.js, but can't get the updated value in vue view

I am using nuxtjs for myProject, i useing axios to get auth information in minddleware/check-auth.js and commit the flag to store.state.isSEPCNavFlag;
But i can't get the updated isSEPCNavFlag value in vue view, the value is always null.
anyone can help me? please...
store/index.js
import axios from 'axios'
import echarts from 'echarts'
import Vue from 'vue'
import Vuex from 'vuex'
import { saveToken, delToken } from '#/utils/auth'
Vue.use(Vuex)
export const strict = true
export const state = () => ({
authUser: null,
user: '',
locale: null,
locales: ['zh', 'en'],
isMenuHidden: true,
isSEPCNavFlag:null,
})
export const mutations = {
SET_SEPCFLAG: function (state, isSEPCNavFlag) {
state.isSEPCNavFlag = isSEPCNavFlag
},
SET_TOKEN: function (state, token) {
state.authUser = token
},
SET_USER: function (state, user) {
state.user = user
},
SET_MENUS: function (state, menus) {
state.menus = menus
},
SET_LANG (state, locale) {
if (state.locales.indexOf(locale) !== -1) {
state.locale = locale
}
},
TOGGLE_MENU_HIDDEN: function (state) {
state.isMenuHidden = !state.isMenuHidden
}
}
minddleware/check-auth.js
async function toNavTO(token,store) {
var returnData = await funcAsync(token)
var ACNameIndex = returnData.data.indexOf("<AccountName>");
var navFlag = true;
if(ACNameIndex == -1){
navFlag=false
}
store.commit('SET_SEPCFLAG', navFlag)
}
index.vue
mounted(){
this.login();
},
methods: {
login(){
let navFlag = this.$store.state.isSEPCNavFlag;
console.log(navFlag);
this.$store.state.isSEPCNavFlag value always null.
mutations SET_SEPCFLAG console.log

Making Async Calls With Vuex

I'm just starting to learn Vuex here. Until now I've been storing shared data in a store.js file and importing store in every module but this is getting annoying and I'm worried about mutating state.
What I'm struggling with is how to import data from firebase using Vuex. From what I understand only actions can make async calls but only mutations can update the state?
Right now I'm making calls to firebase from my mutations object and it seems to be working fine. Honestly, all the context, commit, dispatch, etc. seems a bit overload. I'd like to just be able to use the minimal amount of Vuex necessary to be productive.
In the docs it looks like I can write some code that updates the state in the mutations object like below, import it into my component in the computed property and then just trigger a state update using store.commit('increment'). This seems like the minimum amount necessary to use Vuex but then where do actions come in? Confused :( Any help on the best way to do this or best practices would be appreciated!
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
My code is below
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const db = firebase.database();
const auth = firebase.auth();
const store = new Vuex.Store({
state: {
userInfo: {},
users: {},
resources: [],
postKey: ''
},
mutations: {
// Get data from a firebase path & put in state object
getResources: function (state) {
var resourcesRef = db.ref('resources');
resourcesRef.on('value', snapshot => {
state.resources.push(snapshot.val());
})
},
getUsers: function (state) {
var usersRef = db.ref('users');
usersRef.on('value', snapshot => {
state.users = snapshot.val();
})
},
toggleSignIn: function (state) {
if (!auth.currentUser) {
console.log("Signing in...");
var provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider).then( result => {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// Set a user
var uid = user.uid;
db.ref('users/' + user.uid).set({
name: user.displayName,
email: user.email,
profilePicture : user.photoURL,
});
state.userInfo = user;
// ...
}).catch( error => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
} else {
console.log('Signing out...');
auth.signOut();
}
}
}
})
export default store
main.js
import Vue from 'vue'
import App from './App'
import store from './store'
new Vue({
el: '#app',
store, // Inject store into all child components
template: '<App/>',
components: { App }
})
App.vue
<template>
<div id="app">
<button v-on:click="toggleSignIn">Click me</button>
</div>
</template>
<script>
import Hello from './components/Hello'
export default {
name: 'app',
components: {
Hello
},
created: function () {
this.$store.commit('getResources'); // Trigger state change
this.$store.commit('getUsers'); // Trigger state change
},
computed: {
state () {
return this.$store.state // Get Vuex state into my component
}
},
methods: {
toggleSignIn () {
this.$store.commit('toggleSignIn'); // Trigger state change
}
}
}
</script>
<style>
</style>
All AJAX should be going into actions instead of mutations. So the process would start by calling your action
...which commits data from the ajax callback to a mutation
...which is responsible for updating the vuex state.
Reference: http://vuex.vuejs.org/en/actions.html
Here is an example:
// vuex store
state: {
savedData: null
},
mutations: {
updateSavedData (state, data) {
state.savedData = data
}
},
actions: {
fetchData ({ commit }) {
this.$http({
url: 'some-endpoint',
method: 'GET'
}).then(function (response) {
commit('updateSavedData', response.data)
}, function () {
console.log('error')
})
}
}
Then to call your ajax, you will have to call the action now by doing this:
store.dispatch('fetchData')
In your case, just replace this.$http({...}).then(...) with your firebase ajax and call your action in the callback.