How to access vuex getters from vue-router and set guards? - vuejs2

I am trying to get into the way of things with Vue but I've got some troubles with:
1: I cannot get access to my getters from router/index.js file. (I can get access to it but it return like a function with returns function with I cannot call and get the value)
2: I cannot set up guard properly. With Angular it's much easier
What am I doing wrong here? Any suggestions?
Router code
/* eslint-disable no-undef */
import Vue from "vue";
import VueRouter from "vue-router";
// import auth from '../store/modules/auth';
import { createNamespacedHelpers } from "vuex";
const { mapGetters } = createNamespacedHelpers("auth");
// import store from '../store';
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Home",
component: () => import("../components/Home.vue"),
meta: { requiresAuth: true }
},
{
path: "/users",
name: "Users",
component: () => import("../components/Users/Users.vue"),
meta: { requiresAuth: true }
},
{
path: "/sign-in",
name: "SignIn",
component: () => import("../components/SignIn/SignIn.vue"),
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
router.beforeEach((to, from, next) => {
const storeGetters = { ...mapGetters(['isAuthenticated', 'authStatus', 'test']) };
const isUserLoggedIn = storeGetters.isAuthenticated;
if (to.matched.some(record => record.meta.requiresAuth)) {
if (isUserLoggedIn) {
console.log('user is authenticated');
to; from;
return next();
} else {
console.log('Access denied!');
next({
path: '/signIn',
query: { redirect: to.fullPath }
});
}
next({
path: '/signIn',
query: { redirect: to.fullPath }
});
} else {
next();
}
})
export default router;
Vuex index
import Vue from "vue";
import Vuex from "vuex";
import modules from "./modules"
Vue.use(Vuex);
export default new Vuex.Store({
strict: true,
modules,
state: {
testState: 'State value'
},
getters: {
test: state => state
}
});
auth module (vuex)
import { apolloClient } from '#/vue-apollo';
import SignInGQL from "#/graphql/signIn.gql";
export default {
namespaced: true,
state: {
token: null,
authStatus: false
},
getters: {
isAuthenticated: (state) => {
console.log('state: ', state);
return !!state.token;
},
authStatus: state => state.authStatus,
test: state => state.authStatus
},
actions: {
async signIn({ commit, dispatch }, formInput) {
try {
const { data } = await apolloClient.mutate({
mutation: SignInGQL,
variables: { ...formInput }
})
const { token } = data.signIn;
await commit('setToken', token);
localStorage.setItem('auth-token', token);
await dispatch('setUser', token);
} catch (e) {
console.error(e)
}
},
async setUser({ commit }, token) {
const encodedPayload = token.split('.')[1];
const { payload } = JSON.parse(atob(encodedPayload));
// TODO: Set User information
await commit('signInUser', payload);
}
},
mutations: {
setToken(state, token) {
state.token = token
},
signInUser(state, user) {
console.log('authStatus: ', state.authStatus)
state.authStatus = true
state.user = { ...user }
console.log('authStatus: ', state.authStatus)
},
logOutUser(state) {
console.log('dispatched logOutUser')
state.authStatus = ''
state.token = '' && localStorage.removeItem('auth-token')
}
}
}

It seems createNamespacedHelpers is just complicating things. Import the store:
import store from '#/store'; // <-- aliased path
Use the getters like this:
const isAuthenticated = store.getters['auth/isAuthenticated'];
const authStatus = store.getters['auth/authStatus'];
const test = store.getters['auth/test'];
The first portion of the string is the Vuex module name, followed by the getter name.
Not only is this simpler to use, it's more readable and clear which module the getter comes from when studying the code.

I faced the same problem...
Every time I tried to retrieve the getter's data inside the router it returned the function itself instead of the desired function's return value.
The solution:
In my code I used to call the createStore method inside the main.js file, but in order to be able to call the store's getters inside the vue-router you need to refactor your code, calling createStore in the same index.js file you declared it:
Before refactoring:
main.js file...
import store from './modules/index.js'
import { createStore } from 'vuex';
const mainStore = createStore(store)
app.use(store)
index.js file (Vuex store)...
const store = { ... store code here ... }
export default store
After refactoring:
main.js file...
import store from './modules/index.js'
app.use(store)
index.js file (Vuex store)...
import { createStore } from 'vuex';
const store = createStore({ ... store code here ... })
export default store

Related

Trouble getting user data inside vue-router from composition

I'm very new to vue.js, I am currently working on my final assignment for university.
I'm trying to get information of my user into my router, this works fine on my usual pages/components, but the techniques used on those files don't seem to work here. I've tried reading through some of the documention for router and composition, but I can't seem to figure out where I'm going wrong. This is my latest attempt as earlier I was not using setup() and getting the error; inject() can only be used inside setup() or functional components.
The problem is occuring with "useAuth," I'm not getting any data, my console.log(isAdmin) is displaying 'undefined,' this should be a boolean true/false.
Router code:
import { createWebHistory, createRouter } from "vue-router";
import Dashboard from "../pages/DashboardSDT.vue";
import Events from "../pages/EventsSDT.vue";
import Results from "../pages/ResultsSDT.vue";
import Admin from "../pages/AdminSDT.vue";
import Settings from "../pages/SettingsSDT.vue";
import Login from "../pages/LoginSDT.vue";
import Register from "../pages/RegisterSDT.vue";
import { getAuth } from "firebase/auth";
import useAuth from "../composition/useAuth";
const routes = [
{
path: "/",
name: "Dashboard",
component: Dashboard
},
{
path: "/Events",
name: "Events",
component: Events
},
{
path: "/Results",
name: "Results",
component: Results
},
{
path: "/Admin",
name: "Admin",
component: Admin,
meta: { onlyAdminUser: true }
},
{
path: "/Settings",
name: "Settings",
component: Settings,
meta: { onlyAuthUser: true }
},
{
path: "/Login",
name: "Login",
component: Login,
meta: { onlyGuestUser: true }
},
{
path: "/Register",
name: "Register",
component: Register,
meta: { onlyGuestUser: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, _, next) => {
const isAuth = !!getAuth().currentUser;
const isAdmin = useAuth.admin;
console.log(isAdmin)
if (to.meta.onlyAuthUser) {
if (isAuth) {
next()
} else {
next({ name: "Login" })
}
// } else if(to.meta.onlyAdminUser) {
// if(isAdmin) {
// next()
// }
// else {
// next({name: "BasicUser"})
// }
} else if (to.meta.onlyGuestUser) {
if (isAuth) {
next({ name: "Dashboard" })
} else {
next()
}
} else {
next()
}
})
export default {
setup() {
return {
...useAuth()
}
},
...router
}
useAuth code:
import { useStore } from 'vuex'
import { computed } from 'vue'
export default function useAuth() {
const store = useStore();
const { state } = store;
const error = computed(() => state.user.auth.error);
const isProcessing = computed(() => state.user.auth.isProcessing);
const isAuthenticated = computed(() => store.getters["user/isAuthenticated"]);
const user = computed(() => state.user.data);
const admin = computed(() => state.user.data.admin);
return {
error,
isProcessing,
isAuthenticated,
user,
admin
}
}
vue-router's index file is not like a vue component file and does not have a setup() function. I've never tried but it's unlikely you can use composable functions either, especially when using vue composition API functions like computed()
You can however import the vuex store and access all it's state, getters, etc. like you want.
import store from '/store/index.js'; // or wherever your store lives
Then inside your router guard
const isAuthenticated = store.getters["user/isAuthenticated"];
const isProcessing = store.state.user.auth.isProcessing
// ...etc

vuex error with quasar $store.auth is undefined

I am trying to use vuex with Quasar. I have created an authentication module as below.
// src/store/auth/index.js
import { api } from 'boot/axios';
export default {
state: {
user: null,
},
getters: {
isAuthenticated: state => !!state.user,
StateUser: state => state.user,
},
mutations: {
setUser(state, username){
state.user = username
},
LogOut(state){
state.user = null
},
},
actions: {
LOGIN: ({ commit }, payload) => {
return new Promise((resolve, reject) => {
api
.post(`/api/login`, payload)
.then(({ data, status }) => {
if (status === 200) {
commit('setUser', data.refresh_token)
resolve(true);
}
})
.catch(error => {
reject(error);
});
});
},
}
}
I imported it in the store
// src/store/index.js
import { store } from 'quasar/wrappers'
import { createStore } from 'vuex'
import auth from './auth'
export default store(function (/* { ssrContext } */) {
const Store = createStore({
modules: {
auth:auth
},
// enable strict mode (adds overhead!)
// for dev mode and --debug builds only
strict: process.env.DEBUGGING
})
return Store
})
And I imported it into MainLayout to check if the user is logged in.
// src/layouts/MainLayout
<template>
</template>
<script>
import { ref, onMounted } from 'vue'
import packageInfo from '../../package.json'
import { useStore } from 'vuex'
export default {
name: 'MainLayout',
setup () {
const $store = useStore;
const connected = ref(false);
function checkLogin(){
//console.log($store)
return connected.value = $store.auth.isAuthenticated
};
onMounted(()=> {
checkLogin();
});
return {
appName: packageInfo.productName,
link:ref('dashboard'),
drawer: ref(false),
miniState: ref(true),
checkLogin,
}
}
}
</script>
But every time, I get the same error :
$store.auth is undefined
I tried to follow the quasar documentation, but I can't. Can anyone tell me what I am doing wrong please?
Thank you.
Someone helped me to find the solution. My error is to have written const $store = useStore instead of const $store = useStore(). Thanks

How to access namespaced vuex inside router

i tried to access vuex getters with namespaced modules inside routers.js but the getters always returning null value while the value is true(user logged in)
here is the example code.
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import auth from './auth'
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
auth
}
});
store/auth/index.js
import axios from "axios"
export default {
namespaced: true,
state: {
token: null,
user: null,
status: null
},
getters: {
authenticated(state) {
return state.token && state.user && state.status;
},
user(state) {
return state.user;
}
},
mutations: {
SET_TOKEN(state, token) {
state.token = token;
},
SET_USER(state, data) {
state.user = data;
},
SET_STATUS(state, status) {
state.status = status
}
},
actions: {
async logIn({ dispatch }, data) {
let response = await axios.post('back/in', data);
return dispatch('attemptLogin', response.data.token)
},
async attemptLogin({ commit, state }, token) {
if (token) {
commit('SET_TOKEN', token);
commit('SET_STATUS', true);
}
if (!state) {
return
}
try {
let response = await axios.get('back/me')
commit('SET_USER', response.data.user)
} catch (e) {
commit('SET_USER', null)
commit('SET_TOKEN', null)
commit('SET_STATUS', null);
}
}
},
}
store/subscriber.js
import store from '../store'
import axios from 'axios'
store.subscribe((mutation) => {
switch (mutation.type) {
case 'auth/SET_TOKEN':
if (mutation.payload) {
axios.defaults.headers.common['Authorization'] = `Bearer ${mutation.payload}`;
localStorage.setItem('token', mutation.payload)
} else {
axios.defaults.headers.common['Authorization'] = null;
localStorage.setItem('token')
}
break;
default:
break;
}
})
router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Login from '../views/Login';
import LoggedInLayout from '../layouts/LoggedInLayout';
import Dashboard from '../views/Dashboard';
import Post from '../views/pages/Post/Index';
import store from '../store'
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
routes: [
{
title: 'Login Page',
path: '/backend/login',
name: 'login',
component: Login,
meta: {
mustLoggedIn: true
}
},
{
path: '/backend',
component: LoggedInLayout,
children: [
{
title: 'Dashboard',
path: '/backend/dashboard',
name: 'dashboard',
component: Dashboard
},
{
path: '/backend/post',
name: 'post',
component: Post
},
],
meta: {
mustLoggedIn: true
}
},
]
});
router.beforeEach(async (to, from, next) => {
if (to.matched.some(record => record.meta.mustLoggedIn)) {
console.log(store.getters['auth/authenticated']); // and everytime i tried to console log, it always return null
if (!store.getters['auth/authenticated']) {
next({
path: '/backend/login'
})
} else {
next('/backend/dashboard')
}
} else {
next()
}
})
export default router;
app.js
require('./bootstrap');
import Vue from 'vue';
import store from './store';
import router from './router'
import MainApp from './layouts/MainApp.vue'
import axios from 'axios';
require('./store/subscriber')
axios.defaults.baseURL = 'http://127.0.0.1:8000/api/';
store.dispatch('auth/attemptLogin', localStorage.getItem('token'));
new Vue({
router: router,
store,
render: h => h(MainApp)
}).$mount('#app');
and yeah, when i tried to validate if the user is loggedin throught console log always returning null value, and i stuck with this problem for 1 hour already, can anyone help me to solve this problem ?
You are just syncing your store with local-storage. You also need to persist it for it to stay on after reload etc.,
You will need to use something like vuex-persist.
https://www.npmjs.com/package/vuex-persist
Maybe you get error on "back/me" ?
Try to add "consol.error" in the catch block!
However, why login page has meta "mustLoggedIn" setted true ?

vuex: unknown getter: articles

I try to implement vuex modules and understand usage. While trying to import modules in my home.vue, I have found this solution:
import { FETCH_INDEX_ARTICLES } from "#/store/types/actions.type.js";
// imports this => export const FETCH_INDEX_ARTICLES = "fetchIndexArticles"
import { mapGetters, mapActions} from 'vuex'
export default {
name: "Home",
data() {
return {}
},
computed: {
...mapGetters(['articles']),
},
methods: {
...mapActions([FETCH_INDEX_ARTICLES])
}
created() {
this.$store.dispatch(FETCH_INDEX_ARTICLES);
}
};
but instead I get
vuex.esm.js?2f62:438 [vuex] unknown action type: fetchIndexArticles
vuex.esm.js?2f62:950 [vuex] unknown getter: articles
store/index.js
export default new Vuex.Store({
modules: {
articles,
}
});
store/modules/articles.js
const state = {
articles: [],
};
const getters = {
articles(state) {
return state.articles;
},
};
const mutations = {
[SET_ARTICLES] (state, pArticles) {
state.article = pArticles
state.errors = {}
}
}
const actions = {
[FETCH_INDEX_ARTICLES] (context) {
context.commit(FETCH_START)
return ApiService
.get('/articlelist/index/')
.then((data) => {
context.commit(SET_ARTICLES, data.articles);
context.commit(FETCH_END)
})
.catch((response) => {
context.commit(SET_ERROR, response.data.errors);
})
}
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
How can I correctly import vuex module?
Thanks
You must specify your modules,
Your way is valid when you import your modules directly into your component
...mapGetters('articles', {
article: 'articles',
})
this.article(2)
https://vuex.vuejs.org/guide/modules.html#binding-helpers-with-namespace
To facilitate the use I use the method dispatch for actions
this.$store.dispatch('articles/FETCH_INDEX_ARTICLES', {anydata})

Vue Router fails to navigate from inside unit tests using jest

I have a router, Home, Login components and unit tests for the Login component.
The logic is: when user is unauthenticated, send him to Login page, once he's authenticated, send him to home page.
The logic works fine in the browser, however, when I run unit tests, I get an exception: thrown: undefined once the login component tries to navigate using this.$router.push('/');
In the console I see the message:
trying to route /login /
and then the exception is thrown once i run next();
Am I missing some setup to have the router working properly in the test environment?
Alternatively: is there a way to mock the next() function passed to the navigation guard?
Here's the router:
import VueRouter from 'vue-router';
import Home from '#/views/Home.vue';
import Login from '#/views/Login.vue';
import { state } from '#/store';
export const routes = [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/login',
name: 'login',
component: Login,
meta: {
noAuthRequired: true,
},
},
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
router.beforeEach((to: any, from: any, next: any) => {
console.log('trying to route', from.fullPath, to.fullPath);
const isAuthed = !!state.user.token;
if (!to.meta.noAuth && !isAuthed) {
next({ name: 'login' });
} else {
next();
}
});
export default router;
The component (relevant part):
import Vue from 'vue';
import Component from 'vue-class-component';
import { axios } from '../plugins/axios';
#Component
export default class App extends Vue {
private credentials = {
email: '',
password: '',
};
private error = '';
private async login() {
try {
const data = await axios.post('http://localhost:5000/api/v1/user/auth', this.credentials);
const token = data.data.payload;
this.$store.dispatch('setUser', { token });
this.error = '';
this.$router.push('/');
} catch (error) {
console.warn(error);
this.error = error;
}
}
}
And the unit test:
import Vue from 'vue';
import Vuetify from 'vuetify';
import AxiosMockAdapter from 'axios-mock-adapter';
import { Wrapper, shallowMount, createLocalVue } from '#vue/test-utils';
import flushPromises from 'flush-promises';
import Vuex, { Store } from 'vuex';
import { axios } from '#/plugins/axios';
import VTest from '#/plugins/directive-test';
import LoginPage from '#/views/Login.vue';
import { mainStore, state, IState } from '#/store';
import VueRouter from 'vue-router';
import router from '#/router';
describe('Login page tests', () => {
let page: Wrapper<Vue>;
let localStore: Store<IState>;
const localVue = createLocalVue();
const maxios = new AxiosMockAdapter(axios);
const vuetify = new Vuetify();
const errorMessage = 'Input payload validation failed';
const emailError = 'Invalid Email format';
const validData = {
email: 'valid#email.com',
password: 'test pass',
};
// in order for "click" action to submit the form,
// the v-btn component must be stubbed out with an HTML button
const VBtn = {
template: '<button type="submit"/>',
};
localVue.use(Vuetify);
localVue.directive('test', VTest);
localVue.use(Vuex);
localVue.use(VueRouter);
beforeAll(() => {
maxios.onPost().reply((body: any) => {
const jsonData = JSON.parse(body.data);
if (jsonData.email !== validData.email) {
return [400, {
message: errorMessage,
errors: { email: emailError },
}];
}
return [200, { payload: 'valid-token' }];
});
});
beforeEach(() => {
try {
localStore = new Vuex.Store({
...mainStore,
state: JSON.parse(JSON.stringify(state)),
});
page = shallowMount(LoginPage, {
store: localStore,
router,
localVue,
vuetify,
stubs: {
VBtn,
},
attachToDocument: true,
sync: false,
});
} catch (error) {
console.warn(error);
}
});
afterEach(() => {
maxios.resetHistory();
page.destroy();
});
const submitLoginForm = async (data: any) => {
page.find('[test-id="LoginEmailField"]').vm.$emit('input', data.email);
page.find('[test-id="LoginPasswordField"]').vm.$emit('input', data.password);
page.find('[test-id="LoginBtn"]').trigger('click');
await flushPromises();
};
it('Redirects user to home page after successful auth', async () => {
await submitLoginForm(validData);
expect(page.vm.$route.path).toEqual('/');
});
});