How to access namespaced vuex inside router - vue.js

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 ?

Related

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 vuex getters from vue-router and set guards?

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

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('/');
});
});

Vue.js and router

I need to use the router object inside a custom method but i keep getting an undefined error
I have registered the router in my App.vue import router from './router' and the router works when routing
Now, i have created an auth.js file to manage user status from a jwt token.
/* global localStorage */
import User from '#/models/User'
import * as MutationTypes from './mutation_types'
import router from '#/router'
const state = {
user: User.from(localStorage.token),
layout: 'home-layout'
}
const getters = {
currentUser (state) {
return state.user
},
layout (state) {
return state.layout
}
}
const mutations = {
[MutationTypes.LOGIN] (state) {
state.user = User.from(localStorage.token)
console.log(state.user)
if(!state.user.role){
state.layout = 'home-layout'
} else if (state.user.role==='USER'){
console.log('sono utente')
state.layout = 'home-layout'
} else if (state.user.role==='ADMIN'){
console.log('sono admin')
state.layout = 'admin-layout'
router.replace(route.query.redirect || "/admin");
}
},
[MutationTypes.LOGOUT] (state) {
state.user = null
state.layout = 'home-layout'
},
SET_LAYOUT (state, payload) {
state.layout = payload
}
}
const actions = {
login ({ commit }) {
commit(MutationTypes.LOGIN)
},
logout ({ commit }) {
commit(MutationTypes.LOGOUT)
}
}
export default {
state,
actions,
mutations,
getters,
router
}
this is the router/index.js file
import Vue from 'vue'
import Router from 'vue-router'
import Home from '#/views/Home'
import Contacts from '#/views/Contacts'
import About from '#/views/About'
import Dashboard from '#/views/admin/Dashboard'
import ObjectList from '#/components/ObjectList'
import Register from '#/views/Register'
Vue.use(Router)
export default new Router({
linkExactActiveClass: 'active', // active class for *exact* links.
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/contacts',
name: 'contacts',
component: Contacts
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/register',
name: 'register',
component: Register
},
{
path: '/admin',
name: 'dashboard',
component: Dashboard
},
...
]
})
so when i need to check the current user status i call the LOGIN action but i keep getting that this.$router is undefined, but it should be globally declared
what is the correct way to do it?
I think this is not what you think it is.
import router from './router' //add correct path
const mutations = {
[MutationTypes.LOGIN] (state) {
state.user = User.from(localStorage.token)
if(!state.user.role){
state.layout = 'home-layout'
} else if (state.user.role==='USER'){
state.layout = 'home-layout'
} else if (state.user.role==='ADMIN'){
state.layout = 'admin-layout'
- this.$router.replace(this.$route.query.redirect || "/admin"); //delete
+ router.replace(router.currentRoute.query.redirect || "/admin"); // add
}
},
[MutationTypes.LOGOUT] (state) {
state.user = null
state.layout = 'home-layout'
},
SET_LAYOUT (state, payload) {
state.layout = payload
}
}

vuejs vue-router: TypeError: Cannot read property 'push' of undefined [duplicate]

This question already has answers here:
How to navigate using vue router from Vuex actions
(6 answers)
Closed 3 years ago.
I am having trouble loading a page with vue-router. It appears that my $router var isn't being reached.
When I console log this.$router I receive an undefined. However, console logging this returns the store object in dev tools.
Here are the relevant scripts:
main.js
import Vue from "vue";
import VueCookies from 'vue-cookies';
import App from "./App.vue";
import router from "./router";
import { store } from "./store/store";
import BootstrapVue from "bootstrap-vue";
import "./registerServiceWorker";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import "../css/bracket.min.css";
Vue.use(BootstrapVue);
Vue.use(VueCookies);
// set default config
VueCookies.config('1d');
// set global cookie
VueCookies.set('theme','default');
VueCookies.set('hover-time','1s');
require("../css/bracket.min.css");
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
router.js
import Vue from "vue";
import Router from "vue-router";
// import Home from "#/views/Home.vue";
import Splash from "#/components/Splash.vue";
import Dash from "#/components/Dash.vue";
import Signup from "#/views/Signup.vue";
import finalSignup from "#/components/finalSignup.vue";
import providerDash from "#/views/ProviderDash.vue";
import employeeDash from "#/views/EmployeeDash.vue";
import Login from "#/views/Login.vue";
Vue.use(Router);
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "home",
component: Splash
},
{
path: "/login",
name: "login",
component: Login
},
{
path: "/signup",
name: "signup",
component: Signup
},
{
path: "/provider-full-name",
name: "finalSignup",
component: finalSignup
},
{
path: "/provider-dashboard",
name: "providerDash",
component: providerDash
},
{
path: "/employee-dashboard",
name: "employeeDash",
component: employeeDash
},
{
path: "/about",
name: "about",
component: () =>
import(/* webpackChunkName: "about" */ "./views/About.vue")
}
]
});
userSession.js (vuex module)
-The action in question is named authenticateUserSession
import Axios from "axios";
const userSession = {
namespaced: true,
state: {
email: '',
password: ''
},
mutations: {
SET_EMAIL: (state, payload) => {
state.email = payload;
},
SET_PASSWORD: (state, payload) => {
state.password = payload;
}
},
actions: {
setEmail(context, email) {
context.commit('SET_EMAIL', email)
},
setPassword(context, password) {
context.commit('SET_PASSWORD', password)
},
authenticateUserSession(context, {email, password}) {
context.dispatch('setEmail', email);
context.dispatch('setPassword', password);
Axios.post('http://localhost:3000/api/v1/sessions', {}, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'user-email': context.state.email,
'user-password': context.state.password
}
})
.then((response) => {
// console.log(response.data.locals.token);
// console.log(this.$router);
// console.log(this);
let jwt = response.data.locals.token
window.$cookies.set('jwt', jwt);
this.$router.push("home");
})
.catch(function(error) {
console.log(error);
})
}
},
getters: {
getEmail: (state) => {
return state.email;
},
getPassword: (state) => {
return state.password;
}
}
}
export default userSession;
Why am I missing access to the vue-router variable ($router/this.$router) & unable to render a specified route?
The content in #yuriy636's link in addition to the other links in the post helped me resolve!
I ended up doing the following:
import router from "../../router";
.
.
.
// inside the authenticateUserSession action, in axios response
router.push("home")";
Many thanks #yuriy636!