currentUser not getting set with Vuex - vuejs2

I added some code to my vue project so I can save the state of a user - which is whether he is logged in or not. If the state is not null, I want to display the navbar and footer. I added all the vuex import statements. I am using an axios call to the db which returns a json response. response.data comes back as true/false. If true, I redirect the user to the main page. Then I create a user object called currentUser, but I'm not sure what to base it on, so it is getting set to null. I need to use the state in a few places throughout my app, but it is not getting set. Please someone help. Thanks in advance. (code is below)
User.js:
import JwtDecode from 'jwt-decode'
export default class User {
static from (token) {
try {
let obj = JwtDecode(token)
return new User(obj)
} catch (_) {
return null
}
}
constructor ({username}) {
this.username = username
}
}
App.vue:
<template>
<div id="app">
<template v-if="currentUser">
<Navbar></Navbar>
</template>
<div class="container-fluid">
<router-view></router-view>
<template v-if="currentUser">
<Foot></Foot>
</template>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Navbar from '#/components/Navbar'
import Foot from '#/components/Foot'
export default {
name: 'App',
components: {
Navbar,
Foot
},
computed: {
...mapGetters({ currentUser: 'currentUser' })
},
mutation_types.js:
export const LOGIN = 'LOGIN'
export const LOGOUT = 'LOGOUT'
auth.js:
/* global localStorage */
import User from '#/models/User'
import * as MutationTypes from './mutation_types'
const state = {
user: User.from(localStorage.token)
}
const mutations = {
[MutationTypes.LOGIN] (state) {
state.user = User.from(localStorage.token)
},
[MutationTypes.LOGOUT] (state) {
state.user = null
}
}
const getters = {
currentUser (state) {
return state.user
}
}
const actions = {
login ({ commit }) {
commit(MutationTypes.LOGIN)
},
logout ({ commit }) {
commit(MutationTypes.LOGOUT)
}
}
export default {
state,
mutations,
getters,
actions
}

The user store should only set the default state. AFter making request to validate user. You should use actions and mutations to set the user state. Call the action via store.dispatch("user/login", user) where you return new User(obj).
let obj = JwtDecode(token)
const user = new User(obj)
store.dispatch("user/login", user)
const actions = {
login ({ commit }, userObject) {
commit(MutationTypes.LOGIN, userObject)
},
logout ({ commit }) {
commit(MutationTypes.LOGOUT)
}
}
const mutations = {
[MutationTypes.LOGIN] (state, user) {
state.user = user;
},
[MutationTypes.LOGOUT] (state) {
state.user = null
}
}
On a other note, you have dumb getters. Meaning they just return the state. You can rather call the user object directly out of state. Use getters when you want to modify the return value before returning it.

I took a little look and it seems you use '=' instead of Vue.set() to set your state variable.
Refer to the answer : vuex - is it possible to directly change state, even if not recommended?

Related

Nuxt js / Vuex Cannot get state variables on components which is set by nuxtServerInit

I am trying to get the state variable on components which is set by the nuxtServerInit Axios by get method.
store/state.js
export default () => ({
siteData: null
})
store/mutations.js
import initialState from './state'
const mutations = {
SET_SITE_DATA (state, value) {
state.siteData = {
site_title: value.site_title,
logo: value.logo
}
}
}
export default {
...mutations
}
store/getters.js
const getters = {
siteDetails: state => state.siteData
}
export default {
...getters
}
store/actions.js
const actions = {
async nuxtServerInit ({ commit, dispatch }, ctx) {
try {
const host = ctx.req.headers.host
const res = await this.$axios.post('/vendors/oauth/domain/configuration', { domain: host })
commit('SET_SITE_DATA', res.data.data.site_data)
} catch (err) {
console.error(err)
}
},
export default {
...actions
}
}
store/index.js
import Vuex from 'vuex'
import mutations from './mutations'
import getters from './getters'
import actions from './actions'
import state from './state'
const store = () => {
return new Vuex.Store({
state,
getters,
mutations,
actions
})
}
export default store
Here I set SET_SITE_DATA mutation which set siteData state.
components/Header.vue
<template>
<section class="header sticky-top">
<client-only>
{{ siteDetails }}
{{ $store.getters }}
{ logo }}
</client-only>
</section>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['siteDetails']),
logo () {
console.log(this.$store.state.siteData)
return this.$store.state.siteData
}
}
}
</script>
Console
I don't know what is happening here. You can see I have consoled the values. So when I reload the page I can get the values but after few seconds all values reset to the null. I want to set those values globally so can access them all over the site. I don't want to call API every time a page changes so I used nuxtServerInit so can set values globally when the page reloads once and can access them.

Vuex data just show once then won't display after reloading

why my json data display just only once and after reloading the page it won't show again.
Did I miss something here?
import axios from "axios";
const store = {
careers: []
};
const getters = {
allCareers: (state) => state.careers
};
const mutations = {
RETRIEVE_CAREERS: (state, career) => (state.careers = career),
};
const actions = {
async careers({ commit }) {
try {
const response = await axios.get('http://localhost:9001/career/jobs/');
commit('RETRIEVE_CAREERS', response.data);
} catch (err) {
console.log(err);
}
},
};
export default {
store,
getters,
mutations,
actions
}
and in my component I do this:
import { mapActions, mapGetters } from "vuex";
export default {
computed: {
...mapGetters([
"allCareers"
/* more getters here if necessary */
])
},
methods: {
...mapActions(["careers"])
},
created() {
this.careers();
}
};
and in template I just do this:
<template>
<section>
<v-card>
{{allCareers}}
</v-card>
</section>
</template>
Why it will show only once but won't show after reloading the page?
I don't see anywhere that you "persist" the fetched data. Vuex does not persist the data across reloads, it acts as an in-memory storage
You still have to persist your data to local storage of some sorts like localStorage or indexedDB.
Here is a simple solution:
const store = {
careers: JSON.parse(localStorage.getItem('careers') || '[]');
};
const mutations = {
RETRIEVE_CAREERS: (state, career) => {
state.careers = career;
localStorage.setItem('careers', JSON.stringify(career));
}
};

Using store data in Component pulls in default values, instead of updated ones

I am new to Vue, and am trying to use data from a store in a component. This data gets set when the app loads, and I have confirmed that it is getting set properly.
However, when I try to access this data from the store in a component (I'm using mapState), all I get are the default values for the store, and not the data that was set.
The store I am trying to access is:
//user.js
const defaults = {
emailAddress: '',
gender: 'N/A',
registered: false,
userName: '',
};
const state = {
user: { ...defaults, },
};
const getters = {};
const actions = {
login ({ commit, }) {
api.post(/loginurl).then((response) => {
commit('setUserInfo', { ...defaults, ...response, });
});
},
};
const mutations = {
setUserInfo (state, user) {
state.user = {...state, ...user,};
},
};
const user = {
namespaced: true,
state,
getters,
actions,
mutations,
};
export default user;
I've confirmed that all data is getting set correctly in setUserInfo.
However, when I access this in a component like so:
<template>
<div class="user-info">
<p> {{ user }} </p>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState('user', { user: (state) => state.user, },),
}
};
</script>
All the defaults for user print out in my template, instead of the values that were set in 'setUserInfo.
I will also add, that if I just try to access this via a computed property directly, I also get the defaults. For example:
userInfo () {
return this.$store.state.user;
},
Will also just return the defaults.
Do I need to set up a getter in the store, and then just use that in my component instead? Any help would be much appreciated!!!

async vuex fetch action state filled if using variable in template getting error undefined

i have one async action vuex, im using map getters and component created function to fetch and fill data, if im using this store data inline object in template view console show error undefined, if i try acces variable only without inline object im getting undefined error for inline object, i think this error about async function not blocking main process component fully loaded and after async function filled variable
actions, state
// state
export const state = {
app: null
}
// getters
export const getters = {
app: state => state.app,
}
// mutations
export const mutations = {
[types.FETCH_APP_SUCCESS] (state, { app }) {
state.app = app
},
[types.FETCH_APP_FAILURE] (state) {
state.app = null
},
[types.UPDATE_APP] (state, { app }) {
state.app = app
}
}
async fetchApp ({ commit }) {
try {
const { data } = await axios.get('/api/app/1')
commit(types.FETCH_APP_SUCCESS, { app: data })
} catch (e) {
commit(types.FETCH_APP_FAILURE)
}
}
component
<template>
<div>
{{app.name}}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
middleware: 'auth',
created () {
// i try here async and await
this.$store.dispatch('app/fetchApp')
},
computed: mapGetters({
app: 'app/app'
}),
metaInfo () {
return { title: this.$t('home') }
}
}
</script>
state is filled
variable can see in html
but console this error
app/app is initially null, and your template does not have a null check on app.name, which results in the error you saw. You can either conditionally render app.name in the template:
<template>
<div>
<template v-if="app">
{{app.name}}
</template>
</div>
</template>
Or use the empty string as app/app's initial state instead of null in your store.

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.