Cannot read properties of null (reading 'email') - vue.js

I am unable to fetch the current state that has been store in the component mounting stage.
Here i have initialized a ref variable isAdmin, after that in the mounting stage I want to get the current user email from the state and check if its the same as the email that i want and then i want to toggle isAdmin "true".
But when i am trying to access the email its saying email does not exist.
Here is have attached the vuex store on the right and from where i am accessing its on the left.
check just above
Vuex store.js
import { createStore } from "vuex";
import router from "../router";
import { auth } from "../firebase";
import {
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signOut,
onAuthStateChanged
} from "firebase/auth";
export default createStore({
state: {
user: null,
},
mutations: {
SET_USER(state, user) {
state.user = user;
},
CLEAR_USER(state) {
state.user = null;
},
},
actions: {
async login({ commit }, details) {
const { email, password } = details;
try{
await signInWithEmailAndPassword(auth, email, password)
} catch(error){
alert(error)
return
}
commit('SET_USER',auth.currentUser)
router.push('/')
},
async register({ commit }, details) {
const { email, password } = details;
try{
await createUserWithEmailAndPassword(auth, email, password)
} catch(error){
alert(error)
return
}
commit('SET_USER',auth.currentUser)
router.push('/')
},
async logout({commit}){
await signOut(auth)
commit('CLEAR_USER')
router.push('/login')
},
fetchUser({commit}) {
auth.onAuthStateChanged(async user => {
if(user === null){
commit('CLEAR_USER')
} else {
commit('SET_USER',user)
if(router.isReady() &&
router.currentRoute.value.path === '/login')
{
router.push('/')
}
}
})
}
},
});
Here is the new of the UI with the div that i want show only when that email matches
new div

The fetchUser action is not finishing in time before you try accessing state.user.email in the mounted hook. Instead, make isAdmin a computed property that will set it's value as soon as state.user.email is updated (which itself should be a computed property according to vuex docs).
const userEmail = computed(() => {
return store.state?.user?.email;
})
const isAdmin = computed(() => {
return userEmail.value === "abc#hotmail.com"
})

Related

Unable to store data in vuex "State"

I am unable to store the data from the API in the state, is there any issue in my code?
I am not able to console.log(state.token) or state.token from the mutations.
My Store
export const AUTH_MUTATIONS = {
SET_USER: 'SET_USER',
SET_PAYLOAD: 'SET_PAYLOAD',
LOGOUT: 'LOGOUT',
}
export const state = () => ({
token: null,
userdata: [],
data: [],
})
export const mutations = {
[AUTH_MUTATIONS.SET_USER] (state, { userdata }) {
state.userdata = userdata
},
[AUTH_MUTATIONS.SET_PAYLOAD] (state, { token }) {
state.token = token
},
}
export const actions = {
async login ({ commit, dispatch }, { email_id, password }) {
const { data: {data: { user, token } } } = await this.$axios.post('http://18.xxx.246.xxx:5000/api/v1/users/login',
{
email_id,
password
})
// console.log(user)
// console.log(token)
commit(AUTH_MUTATIONS.SET_USER, user)
commit(AUTH_MUTATIONS.SET_PAYLOAD, token)
// console.log(AUTH_MUTATIONS.SET_USER, user)
},
}
export const getters = {
isAuthenticated: (state) => {
return state.token && state.token !== ''
},
}
In your action you need to take in state in the destructured parameters like this: { commit, dispatch, state } then you will be able to access your state and log state.token.
If it's still null after that point then you should debug it to ensure that you're setting it correctly in the mutation itself.

How to get Vuex updated getters value in Vue custom middleware for permission check?

I have loaded all permissions when the sidebar is loading after login and getters are updated. I can access all permissions from the sidebar component.
Now I want to access all permissions in my middleware. Is it possible? What to do?
Please give a suggestion.
Here is my permission store:
const state = {
permissions: [],
user: [],
}
const getters = {
getPermissions: state => state.permissions,
getUserInfo: state => state.user,
}
const actions = {
userPermission({commit}, data) {
if (data != null) {
axios.get("/api/auth/user", {params: { token: data.token}})
.then(res => {
const per = res.data.data.permissions;
commit("setPermissions", per);
// console.log(res.data.data.permissions);
})
.catch(err => {
console.log(err);
});
}
},
userInfo({commit}, data) {
if (data != null) {
axios.get("/api/auth/user", {params: { token: data.token}})
.then(res => {
const info = res.data.data.user;
commit("setUserInfo", info);
// console.log(res.data.data.user);
})
.catch(err => {
console.log(err);
});
}
},
}
const mutations = {
setPermissions(state, data) {
state.permissions = data;
},
setUserInfo(state, data) {
state.user = data;
}
}
export default {
state,
getters,
actions,
mutations
}
Here is the middleware function:
import store from '../store';
export default (to, from, next) => {
if (isAuthenticated()) {
if (!hasPermissionsNeeded(to)) {
next('admin/permission-denied');
} else {
next();
}
next();
} else {
next('/admin/session/login');
}
};
function isAuthenticated() {
if (localStorage.getItem("userInfo") != null && localStorage.getItem("userInfo").length > 0) {
return true;
} else {
localStorage.removeItem("userInfo");
return false;
}
};
function hasPermissionsNeeded(to) {
var permissions = store.getters.getPermissions;
if(permissions.includes(to.meta.permissions) || to.meta.permissions == '*') {
return true;
} else {
return false;
}
};
Here is the router logic:
path: "/admin/country",
component: () => import("./views/admin/country/country"),
beforeEnter: authenticate,
meta : {
permissions: 'browse country'
}
I can't see where you're dispatching the userPermission action to load the permissions, but I assume you're only dispatching it somewhere that only gets called after the middleware has run. So it looks like the permissions might not have been loaded by the time you're running the middleware. You might want to dispatch the permission in the middleware, wait for it to finish and only then check the permissions. For example:
export default (to, from, next) => {
store.dispatch('userPermission').then(() => {
if (isAuthenticated()) {
...
})

VueJS - VueX : displaying notification after async process

Extract of my Single File Component:
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
data () {
return {
firstname: this.$store.getters.user.firstName,
lastname: this.$store.getters.user.lastName,
}
},
methods: {
...mapActions([
'updateUserProfile'
]),
// Function called when user click on the "Save changes" btn
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
lastName: this.lastname
}
this.updateUserProfile(userData);
}
}
}
</script>
In my VueX store:
I already manage a LOADING state which is used to display a loading spinner.
Now, i would like to display a notification widget programmatically by using toastr library: toastr.success("Your profile has been updated");
Where should I place this code ? I suppose that is not a good practice to put this code directly on the updateUserProfile function of the store, but more on the Single File Component where the call is made ?
/*
* Action used to fetch user data from backend
*/
updateUserProfile ({commit, state}, userData) {
if (!state.jwtToken) {
return
}
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Store user data in local storage
localStorage.setItem('user', JSON.stringify(res.data.data));
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
});
}
You can return a Promise from action
updateUserProfile ({commit, state}, userData) {
if (!state.jwtToken) {
return
}
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
return axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Store user data in local storage
localStorage.setItem('user', JSON.stringify(res.data.data));
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
return res.data.data
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
throw error
});
}
and then in Vue component:
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
lastName: this.lastname
}
this.updateUserProfile(userData)
.then(data => {
toastr.success("Your profile has been updated");
})
.catch(error => {
console.error(error)
})
}
You should probably return the promise from your action:
/*
* Action used to fetch user data from backend
*/
updateUserProfile ({commit, state}, userData) {
if (!state.jwtToken) {
throw new Error('unauthenticated')
}
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
return axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Store user data in local storage
localStorage.setItem('user', JSON.stringify(res.data.data));
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
return res.data.data
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
throw error
});
}
And then use this promise in your component:
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
lastName: this.lastname
}
this.updateUserProfile(userData).then(user => {
toastr.success("Your profile has been updated")
}).catch(error => {
toastr.error("Something bad happened")
})
}

Setting value to input field using Vuex store modules

I have a vuex in module mode that fetching the data of a user:
store/modules/users.js
import axios from "axios";
export const state = () => ({
user: {}
});
// Sets the values of data in states
export const mutations = {
SET_USER(state, user) {
state.user = user;
}
};
export const actions = {
fetchUser({ commit }, id) {
console.log(`Fetching User with ID: ${id}`);
return axios.get(`${process.env.BASE_URL}/users/${id}`)
.then(response => {
commit("SET_USER", response.data.data.result);
})
.catch(err => {
console.log(err);
});
}
};
// retrieves the data from the state
export const getters = {
getUser(state) {
return state.user;
}
};
then on my template pages/users/_id/index.vue
<b-form-input v-model="name" type="text"></b-form-input>
export default {
data() {
return {
name: ""
}
},
created() {
// fetch user from API
this.$store.dispatch("fetchUser", this.$route.params.id);
}
}
Now I check the getters I have object getUser and I can see the attribute. How can I assign the name value from vuex getters to the input field?
watcher is probably what you need
export default {
// ...
watch: {
'$store.getters.getUser'(user) {
this.name = user.name;
},
},
}
While Jacob's answer isn't necessarily incorrect, it's better practice to use a computed property instead. You can read about that here
computed: {
user(){
return this.$store.getters.getUser
}
}
Then access name via {{user.name}} or create a name computed property
computed: {
name(){
return this.$store.getters.getUser.name
}
}
Edit: fiddle as example https://jsfiddle.net/uy47cdnw/
Edit2: Please not that if you want to mutate object via that input field, you should use the link Jacob provided.

How access Vue Js computed properties?

I have the next code with vuejs, i call axios method post and set the authenticated user correctly(cosole show the user), but when i call the computed property in the component the user is empty
export default {
data() {
return {
isAuth: null,
}
},
computed: {
authenticatedUser () {
return this.getAuthenticatedUser()
}
},
created() {
this.isAuth = this.$auth.isAuthenticated()
this.setAuthenticatedUser()
},
methods: {
setAuthenticatedUser () {
axios.get('/api/user')
.then(response => {
this.$auth.setAuthenticatedUser(response.data)
console.log(this.$auth.getAuthenticatedUser())
})
},
getAuthenticatedUser(){
return this.$auth.getAuthenticatedUser()
}
},
router
}
And this my code for get the authenticated user
export default function (Vue) {
let authenticatedUser = {};
Vue.auth = {
//set token
setToken (token, expiration) {
localStorage.setItem('token', token)
localStorage.setItem('expiration', expiration)
},
//get token
getToken() {
var token = localStorage.getItem('token')
var expiration = localStorage.getItem('expiration')
if( !token || !expiration)
return null
if(Date.now() > parseInt(expiration)){
this.destroyToken()
return null
}
else{
return token
}
},
//destroy token
destroyToken() {
localStorage.removeItem('token')
localStorage.removeItem('expiration')
},
//isAuthenticated
isAuthenticated() {
if(this.getToken())
return true
else
return false
},
setAuthenticatedUser(data){
return authenticatedUser = data;
},
getAuthenticatedUser(){
return authenticatedUser;
},
}
Object.defineProperties(Vue.prototype, {
$auth: {
get() {
return Vue.auth
}
}
})
}
When i not use the computed property
When i use the computed property in the model
Your computed property won't be updated because this.$auth is out of the instance’s scope (i.e. not reactive).
I would use vuex, putting the user inside the global state:
const store = new Vuex.Store({
state: {
user: {}
},
mutations: {
user (state, user) {
state.user = user
}
}
})
and then watch changes in your component:
import store from 'path/to/store'
store.watch(state => {
return state.user
}, () => {
// process authenticated user
})
Make $auth another Vue instance and install it as a plugin, this way, it will be accessible from any other Vue instance.
function Auth(Vue) {
let auth = new Vue({
data: {
// your auth data
authenticatedUser = {}, // This one is now reactive
},
computed: {
// your auth computed properties
},
methods: {
// your auth methods
setAuthenticatedUser(data){
return this.authenticatedUser = data
},
}
})
Vue.prototype.$auth = auth
}
To use this plugin, simply call:
Vue.use(Auth)
Now, you can access the authenticated user from any Vue component like this:
this.$auth.authenticatedUser