Vuex getter always return null - vue.js

When i use vuex getter in my vue.js component it return null for me.
Here is my code
MainLayout.vue
<script>
import NavBar from '#/components/NavBar.vue'
import ToolBar from "#/components/ToolBar"
import { mapActions, mapGetters } from 'vuex'
export default {
name: "MainLayout",
components : {
ToolBar, NavBar
},
data: () => ({
drawer: null,
}),
computed: {
...mapGetters([
'error',
]),
},
methods: {
close() {
this.$store.commit('SET_ERROR', null)
},
}
}
</script>
<template>
<div id="main">
<v-navigation-drawer clipped v-model="drawer" app>
<nav-bar></nav-bar>
</v-navigation-drawer>
<tool-bar #toggleDrawer="drawer = !drawer"/>
<v-content>
<v-container class="fill-height" fluid>
<router-view></router-view>
</v-container>
</v-content>
<v-snackbar :timeout="0" :value="error">
{{ error }}
<v-btn color="red" text #click="close">
Close
</v-btn>
</v-snackbar>
</div>
</template>
<style scoped>
</style>
Here is NavBar.vue
<script>
import { mapGetters } from 'vuex'
export default {
data: () => ({
}),
computed: {
...mapGetters([
'authUser'
]),
isAdmin() {
return this.authUser.role.name == 'admin'
},
}
}
</script>
Vuex module auth.js
import api from '#/api'
import {clearAccessToken, setAccessToken} from '#/auth'
import router from '#/router'
const state = {
loading: null,
user: null
}
const mutations = {
SET_LOADING: (state, loading) => {
state.loading = loading
},
SET_USER: (state, user) => {
state.user = user
}
}
const getters = {
loading: state => {
return state.loading
},
loggedIn: (state) => {
return !!state.user
},
authUser: (state) => {
return state.user
},
}
const actions = {
async login({commit, dispatch }, user) {
commit('SET_LOADING', true)
try {
const data = await api.post('/api/auth/login', { user })
setAccessToken(data.token)
await dispatch('getUser')
commit('SET_LOADING', false)
router.push('/')
} catch (e) {
commit('SET_LOADING', false)
dispatch('handleError', e)
}
},
async getUser({commit, dispatch}) {
try {
const user = await api.get('/api/auth/user')
commit('SET_USER', user.data)
return user
} catch (e) {
clearAccessToken()
dispatch('handleError', e)
}
},
async logout({commit, dispatch}) {
try {
await api.post('/api/auth/logout')
clearAccessToken()
router.push('/login')
} catch(e) {
dispatch('handleError', e)
}
}
}
export default {
namespaced: false,
state,
getters,
actions,
mutations,
}
When i run this code i have next error
[Vue warn]: Error in render: "TypeError: Cannot read property 'role' of null"
But if i add code
isAdmin() {
return this.authUser.role.name == 'admin'
},
in ToolBaar component (and remove from NavBar)
<script>
import { mapGetters } from 'vuex'
export default {
methods: {
toggleDrawer () {
this.$emit('toggleDrawer')
},
logout() {
this.$store.dispatch('logout')
}
},
computed: {
...mapGetters([
'loggedIn',
'authUser'
]),
fullName() {
return this.authUser.first_name + ' ' + this.authUser.last_name
},
isAdmin() {
return this.authUser.role.name == 'admin'
}
},
}
</script>
Then it work good, without any error, so i dont know what is the issue here, in one component code work good, and in another it doesnt, also if i add it in MainLayout component and pass isAdmin as props then it also work. Help me pls fix this.
Also, i dispatch user in router hook
router.beforeEach(async(to, from, next) => {
const needAuth = to.matched.some(record => record.meta.auth)
function redirectToLogin() {
next({
path: '/login',
query: { redirect: to.fullPath },
})
}
if (!hasToken() && needAuth) {
return redirectToLogin()
}
if (hasToken() && !store.getters.loggedIn) {
try {
const user = await store.dispatch('getUser')
if (!user) {
return redirectToLogin()
}
} catch(e) {}
}
next()
})

You should guard your access of authUser with loggedIn. For example
isAdmin() {
return this.loggedIn && this.authUser.role.name == 'admin'
}

Related

TypeError when rendering property of Vue-test setData object

I'm running into a strange situation and can't figure out why. Basically in my HTML, if I render 'actor[0]', the test runs fine and the console log shows the entire 'actor' object present in setData
However, if I try to access a property of the 'actor' object, like actor[0].firstname, the test throws a TypeError-can't-read-property-of-undefined.
The weird part is console logging 'wrapper.vm.actor[0].firstname' works fine so it doesn't seem like an async issue.
myapps.spec.js
import { mount } from "#vue/test-utils";
import MyApps from "#/pages/myapps.vue";
import Vuetify from "vuetify";
describe("Testing Myapps", () => {
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
it("Checks SideBarComponent is rendered", async () => {
const wrapper = mount(MyApps, {
// localVue,
vuetify,
mocks: {
$vuetify: { breakpoint: {} }
},
stubs: {
SideBarComponent: true,
FooterComponent: true
}
});
await wrapper.setData({
actor: [
{
firstname: "bob",
lastname: "bob",
group: "actors"
}
]
});
console.log(wrapper.html()); // TypeError: Cannot read property 'first name' of undefined
console.log(wrapper.vm.actor[0].firstname); // "bob" if I set the template back to actor[0] so the test runs
});
});
myapps.vue
<template>
<div>
<v-app>
<v-col cols="3">
<v-btn
text
#click="getAcceptedApplications"
elevation="0"
block
>Accepted {{actor[0].firstname}}</v-btn>
</v-col>
</v-app>
</div>
</template>
<script>
export default {
async asyncData({ params, $axios, store }) {
try {
const body = store.getters.loggedInUser.id;
const [applications, actor] = await Promise.all([
$axios.$get(`/api/v1/apps/`, {
params: {
user: body
}
}),
$axios.$get(`/api/v1/actors/`, {
params: {
user: body
}
})
]);
return { applications, actor };
if (applications.length == 0) {
const hasApps = false;
}
} catch (error) {
if (error.response.status === 403) {
const hasPermission = false;
console.log(hasPermission, "perm");
console.error(error);
return { hasPermission };
}
}
},
data() {
return {
actor: []
};
}
};
</script>
Try not to use setData method, pass data while mounting the component like that:
const wrapper = mount(MyApps, {
vuetify,
mocks: {
$vuetify: { breakpoint: {} }
},
stubs: {
SideBarComponent: true,
FooterComponent: true
}
data: () => ({
actor: [
{
firstname: "bob",
lastname: "bob",
group: "actors"
}
]
})
})

beforeRouteEnter function and Vuex problem

In the quasar project, I have a Vuex function "asyncValidateToken" that checks whether the user is logged in to the system. It is located in the file "src/store/index.js". The file contains the following code:
import Vue from 'vue'
import Vuex from 'vuex'
import { api } from 'boot/axios'
Vue.use(Vuex)
export default function (/* { ssrContext } */) {
const Store = new Vuex.Store({
state: {
isLogin: false
},
mutations: {
changeIsLogin (state, payload) {
state.isLogin = payload;
}
},
actions: {
asyncValidateToken: async (context, payload) => {
await api.post('/accounts/token', '', {
headers: {
'Authorization': `Bearer ${localStorage.token}`,
}
})
.then(response => {
if (response.data == localStorage.userId) {
context.commit('changeIsLogin', true);
return true;
} else {
context.commit('changeIsLogin', false);
return false;
}
})
.catch(error => {
context.commit('changeIsLogin', false);
return false;
});
}
}
})
return Store
}
The page "Results.vue" where the route protection is used via the function "beforeRouteEnter"
<template>
<q-page class="flex flex-center">
<div>
<charts />
<feedback />
</div>
</q-page>
</template>
<script>
import Charts from 'src/components/Charts.vue'
import Feedback from 'src/components/Feedback.vue'
import store from 'src/store/index.js'
export default {
name: 'Results',
components: {
Charts,
Feedback
},
beforeRouteEnter (to, fromR, next) {
if (store.dispatch('asyncValidateToken')) {
next();
} else { this.$router.push('/login'); }
}
}
</script>
I get an error "src_store_index_js__WEBPACK_IMPORTED_MODULE_2__.default.dispatch is not a function
at beforeRouteEnter (Results.vue?82a0:23)
at routeEnterGuard (vue-router.esm.js?85f8:2333)". The construction "this.$store.dispatch('asyncValidateToken')" also does not work. Why?
Try
store().dispatch('')
Why?
Because your store.js module is exporting a function as default, and it returns the store.

Variable not updated after vuex mutation

I am creating a settings page, where I fetch some data from the API and I am using Vuex to handle mutations.
I can see that the Vuex completes properly, but value for my dailyCount variable doesn't update in frontend.
This is my Settings component:
<template>
<div>
<div class="row col">
<h1>Settings</h1>
</div>
<div class="row col">
<div class="well">
<form class="form-inline">
<input type="number" v-model="dailyCount" />
{{ dailyCount }}
</form>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'settings',
data () {
return {
dailyCount: 500
};
},
created () {
this.$store.dispatch('settings/fetchSetting');
},
computed: {
isLoading() {
return this.$store.getters['user/isLoading'];
},
hasError() {
return this.$store.getters['user/hasError'];
},
error() {
return this.$store.getters['user/error'];
},
},
}
</script>
I do mutations here:
import SettingsAPI from '../api/settings';
export default {
namespaced: true,
state: {
isLoading: false,
error: null,
settings: null,
},
getters: {
isLoading (state) {
return state.isLoading;
},
hasError (state) {
return state.error !== null;
},
error (state) {
return state.error;
},
user (state) {
return state.user;
},
},
mutations: {
['FETCHING_SETTINGS'](state) {
state.isLoading = true;
state.error = null;
state.settings = null;
},
['FETCHING_SETTINGS_SUCCESS'](state, settings) {
state.isLoading = false;
state.error = null;
state.settings = settings;
},
['FETCHING_SETTINGS_ERROR'](state, error) {
state.isLoading = false;
state.error = error;
state.settings = null;
},
},
actions: {
fetchSetting ({commit}) {
commit('FETCHING_SETTINGS');
return SettingsAPI.get()
.then(res => {commit('FETCHING_SETTINGS_SUCCESS', res.data);})
.catch(err => commit('FETCHING_SETTINGS_ERROR', err));
},
},
}
And call to a server is done here (api/settings.js - it is imported in mutation file):
import axios from 'axios';
export default {
get() {
return axios.get('/user');
},
}
Can you see what am I doing wrong? I am trying to debug it using Vuejs debug toolbar, but all seems to work fine.
You need to get store state from vuex and inject to Vue component, either by this.$store.state or this.$store.getters.
For example:
<script>
export default {
name: 'settings',
data () {
return {
dailyCount: 500
};
},
created () {
this.$store.dispatch('settings/fetchSetting');
},
computed: {
isLoading() {
return this.$store.getters['user/isLoading'];
},
hasError() {
return this.$store.getters['user/hasError'];
},
error() {
return this.$store.getters['user/error'];
},
settings() {
return this.$store.state.settings
}
},
watch: {
settings () {
this.dailyCount = this.settings.dailyCount
}
}
}
</script>

Correct way to cache data in vuex

I am trying to asynchronously load data into vuex that is static but is used by multiple routes.I only want to fetch the data once and only when a route that needs it is visited. This is what I'm currently doing but I'm not sure if this is the correct convention or if there is a better/more Vueish way.
// store.js
export default new Vuex.Store({
state: {
_myData: null,
},
getters: {
myData: (state) => new Promise((resolve,reject) => {
if(state._myData){
resolve(state._myData);
} else {
axios.get('http://myData.com/')
.then(myData => {
state._myData = myData;
resolve(state._myData);
});
}
})
}
});
// ProfilePage.vue
<template>
<div>{{myData}}</div>
</template>
<script>
export default {
data() {
return {
myData:null
}
},
async created(){
this.myData = await this.$store.getters.myData;
}
}
</script>
// AboutPage.vue
<template>
<div>{{myData}}</div>
</template>
<script>
export default {
data() {
return {
myData:null
}
},
async created(){
this.myData = await this.$store.getters.myData;
}
}
</script>
There is a correct way to do what you want but it is not the way you are doing it. Vue is quite strict on "Do not mutate vuex store state outside mutation handlers."
This means you should only alter the store state through a mutation, then use your getter only to get the data. You should also use an action to commit the mutation. So for what you are trying to do you should try it like this.
// AnyPage.vue
<template>
<div>{{myData}}</div>
</template>
<script>
export default {
data() {
return {
myData:null
}
},
async created(){
if(this.$store.state._myData === null) {
await this.$store.dispatch('getData')
}
this.myData = this.$store.getters.myData;
}
}
</script>
then in your store:
// store.js
export default new Vuex.Store({
state: {
_myData: null,
},
getters: {
myData: (state) => state._myData,
}
mutations: {
SET_DATA(state, data) {
state._myData = data
}
}
actions: {
getData({ context }){
axios.get('http://myData.com/')
.then(res => {
context.commit('SET_DATA', res)
})
}
}
}
});
You should read up in the docs which covers it all pretty well.
Action handlers receive a context object which exposes the same set of methods/properties on the store instance, so you can call context.commit to commit a mutation, or access the state and getters via context.state and context.getters.
https://vuex.vuejs.org/guide/actions.html
try this:
// AnyPage.vue
<template>
<div>{{myData}}</div>
</template>
<script>
export default {
computed: {
myData () {
return this.$store.state.myData
}
},
mounted () {
this.$store.dispatch('getData')
}
}
</script>
in store file:
// store.js
export default new Vuex.Store({
state: {
myData: null,
},
mutations: {
SET_DATA(state, data) {
state.myData = data
}
}
actions: {
getData({ context }){
if (context.state.myData === null) {
axios.get('http://myData.com/')
.then(res => {
context.commit('SET_DATA', res)
})
}
}
}
}
});

Pre-fetch data using vuex and vue-resource

I'm building an app following this structure: http://vuex.vuejs.org/en/structure.html
My components/App.vue like this:
<template>
<div id="app">
<course :courses="courses"></course>
</div>
</template>
<script>
import Course from './course.vue'
import { addCourses } from '../vuex/actions'
export default {
vuex: {
getters: {
courses: state => state.courses,
},
actions: {
addCourses,
}
},
ready() {
this.addCourses(this.fetchCourses())
},
components: { Course },
methods: {
fetchCourses() {
// what do I have to do here
}
}
}
</script>
How can I fetch the data and set it to the state.courses ?
Thanks
I've just figured it out:
in /components/App.vue ready function, I just call:
ready() {
this.addCourses()
},
in vuex/actions.js:
import Vue from 'vue'
export const addCourses = ({ dispatch }) => {
Vue.http.get('/api/v1/courses')
.then(response => {
let courses = response.json()
courses.map(course => {
course.checked = false
return course
})
dispatch('ADD_COURSES', courses)
})
}
and in vuex/store.js:
const mutations = {
ADD_COURSES (state, courses) {
state.courses = courses
}
}