Background
I have created a router/index.js file and created a router for my Vue application. I added VueX to the application and am using it throughout some of the components. I would like to use a getter inside of router to check the permissions of a user logged in but I am having trouble importing VueX correctly.
Problem
When I important my store and module to the router file index.js, I am trying to use this.$store.registerModule(). When I try this I get undefined on this.$store.
Example
import Vue from 'vue';
import Router from 'vue-router';
import { mapGetters } from 'vuex';
import authentication from '../store/modules/authentication';
import store from '../store';
if (!(store && store.state && store.state[name])) {
this.$store.registerModule(name, authentication);
}
This logic works in any Vue component I use it in but it does not work in the router file.
My goal is to be able to check state in VueX like this,
const getters = {
...mapGetters('authentication', ['IS_LOGGED_IN'])
};
const router = new Router({
{
path: '/admin',
name: 'Admin',
component: Admin,
meta: {
requiresAuth: true,
isAdmin: true
}
]
});
router.beforeEach((to, from, next) => {
if (
to.matched.some(record => record.meta.requiresAuth) &&
to.matched.some(record => record.meta.isAdmin)
) {
if (!getters.IS_LOGGED_IN && !getters.IS_ADMIN) {
next({
path: '/login',
params: { nextUrl: to.fullPath }
});
} else {
next();
}
} else if (to.matched.some(record => record.meta.requiresAuth)) {
if (!getters.IS_LOGGED_IN) {
next({
path: '/login',
params: { nextUrl: to.fullPath }
});
} else {
next();
}
} else if (to.matched.some(record => record.meta.guest)) {
next();
} else {
next();
}
});
export default router;
Question
What is the correct way to use VueX with Vue Router?
Things I tried
I tried using,
store.registerModule(name, authentication);
This throws undefined on, registerModule
Related
I'm having an issue with a linting error in a vue.js project. The error that I get looks like this:
/Users/mikecuddy/Desktop/coding/data_science_projects/statues/client/src/store/modules/common.js
4:1 error Dependency cycle via #/store/index:4 import/no-cycle
I have no idea how to get rid of this error. I tried renaming files, using this.$router and this.$store with no luck. Here is some of my code:
router -> index.js:
The data path is the main one I want to get to. Notice that I have the store import files commented out - that does get rid of the dependency error but then I have issues with doing something like:
this.$store.state.common.loginFlag
as opposed as importing the store and doing this:
store.state.common.loginFlag
import Vue from 'vue';
import VueRouter from 'vue-router';
// import store from '../store/index.js';
// import store from '#/store/index';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/data',
name: 'Data',
component: () => import('../views/Data.vue'),
beforeEnter: (to, from, next) => {
if (this.$store.state.common.loginFlag === false) {
next('/login');
} else {
next();
}
},
beforeRouteLeave: (to, from, next) => {
if (this.$store.state.common.loginFlag === false) {
next('/login');
} else {
next();
}
},
},
];
const router = new VueRouter({
routes,
});
export default router;
store/modules/common.js:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
import router from '../../router';
Vue.use(Vuex);
const data = {
userNotFound: false,
passwordNoMatch: false,
loginFlag: false,
};
const getters = {
userNotFound: (state) => state.userNotFound,
passwordNoMatch: (state) => state.passwordNoMatch,
loginFlag: (state) => state.loginFlag,
};
const actions = {
login: ({ commit }, { payload }) => {
const path = 'http://localhost:5000/login';
axios.post(path, payload)
.then((res) => {
if (res.data.login_flag) {
commit('session/setUserObject', res.data.user, { root: true });
commit('setLoginFlag', res.data.login_flag);
// Tried this:
router.push{ name: 'Data' }
// As well as trying this:
this.$router.push({ name: 'Data' });
}
commit('setNoPasswordMatch', res.data.Password_no_match);
commit('setUserNotFound', res.data.Not_found);
})
.catch((error) => {
console.log(error);
});
},
};
// I have mutations but did not think they'd be needed
const mutations = {};
export default {
namespaced: true,
state: data,
getters,
actions,
mutations,
};
In the common.js file I've tried commenting out:
import router from '../../router';
and that seemed to work - got the Dependency cycle error to go away and in the router/index.js file I was able to get to the route but had an issue with this.$store.state.common.loginFlag when I commented out import store from '#/store/index'; If I leave in the import of: import store from '#/store/index';
then I get the dependency cycle error.
I've also found some help at these other stack pages:
TypeError: Cannot read properties of undefined (reading '$router') vuejs
dependency cycle detected import/no-cycle
I will say that I hate using linters and that's what's giving me the problem here.
Here is the code for store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import common from './modules/common';
import session from './modules/session';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
common,
session,
},
});
Looks like the reason for the dependency cycle here is when you are importing router setup in the store module, and the router in turn imports the whole store. It's okay to use store in router, but try to move routing/redirect logic (these lines):
// Tried this:
router.push{ name: 'Data' }
// As well as trying this:
this.$router.push({ name: 'Data' });
from /modules/common.js to the component or global router hook level, so you avoid router import in the store module.
What is the exact problem?
I'm building authentication app with vue.js. When I was dealing with secure routes I encountered the problem that the function 'beforeEnter' takes default values from vuex. It's problematic because of it makes me unable to create secure routes. I would be glad for your help!
Here is route file
import { createRouter, createWebHistory } from 'vue-router'
import store from '../store/index'
const routes = [
{
path: '/globalData',
name: 'GlobalData',
component: () => import('../views/GlobalData.vue'),
beforeEnter: (_, __, next) => {
if (!store.state.isLoggedIn) {
next('/about')
}else {
next()
}
}
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
Here is my store
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
isLoggedIn: false,
}
},
mutations: {
setIsLoggedIn(state, bool) {
state.isLoggedIn = bool;
}
}
})
export default store;
Here is how isLoggedIn changes
import{ onBeforeMount, ref } from 'vue'
import fire from '#/firebase';
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
onBeforeMount(() => {
fire.auth().onAuthStateChanged((user) => {
if (user) {
store.commit('setIsLoggedIn', true)
}else {
store.commit('setIsLoggedIn', false)
}
})
})
}
}
I think that my problem can be somehow connected with the fact that before vuex is updated I want to redirect to another route and consequently it takes default values. Thank you for your help!
A common pattern is to have a route that handles user login and this route is public. From this route you could redirect to another routes after the login, and any protected route redirects to the login when there is no logged user.
The logic goes:
Unauthorized user lands in the protected route A, before entering, the router redirects to login, after the successfull authorization the login redirects to A again.
How does login knows to which route redirects after login?? You can send the name of the redirected route as a route param or query, or you can store the expected route in the store and login redirects to whatever finds in the params, query or store.
I am trying to solve this but I can't, I have a website built with Laravel and Vuejs:
This is my app.js
import 'bootstrap';
import './axios';
import Vue from 'vue';
import VueRouter from 'vue-router';
import store from './store';
import router from './router';
store.dispatch('checkAuth');
const app = new Vue({
el: '#app',
store ,
router
});
this is my router.js:
import VueRouter from 'vue-router';
import Login from './components/Login';
import Register from './components/Register';
import Main from './components/Main';
const checkLogin = (to, from, next) =>{
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.getters.isAuthenticated) {
next({name: 'login' })
} else {
next()
}
} else if (to.matched.some(record => record.meta.requiresVisitor)) {
if (store.getters.isAuthenticated) {
next({name: 'home' })
} else {
next()
}
} else {
next()
}
}
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/',
beforeEnter: checkLogin,
component: Main,
meta: { requiresAuth: true },
children : [
{
path: "",
name: "home",
component: PostsList
},
....
]
},
{
path: '/login',
beforeEnter: checkLogin,
component: Login,
name:'login',
meta: { requiresVisitor: true }
},
{
path: '/register',
beforeEnter: checkLogin,
component: Register,
name: 'register',
meta: { requiresVisitor: true }
}
]
});
and this is my store:
import Vue from 'vue';
const state = {
user: null,
isAuthenticated: false
};
const getters = {
isAuthenticated(state){
return state.isAuthenticated;
},
currentUser(state){
return state.user;
}
};
async login({commit, dispatch}, credentials) {
await axios.get('/sanctum/csrf-cookie');
const {data} = await axios.post("/login", credentials);
commit('setAuth', data);
},
async checkAuth({commit}) {
const {data} = await axios.get('/api/user');
commit('setAuth', data);
}
}
const mutations = {
setAuth(state, payload) {
state.user = payload.user;
state.isAuthenticated = Boolean(payload.user);
}
};
Then the problem arrives when I refresh the page, it calls the action:
store.dispatch('checkAuth');
then it should wait until the action is done because I did this in the action:
const {data} = await axios.get('/api/user');
But no, it doesn't wait because vue-router is executed and as the user state is not set yet , store.getters.isAuthenticated is false then it redirects to /login, then when I check the vue tools in the browser and see that the state was set correctly even the request to api/user returns the user correctly, Because before that I logged in a user, I need that vue-router waits until the vuex state is set. what can I do? Thank you.
I was able to solve this problem, but I don't know if this is the best way to do it:
in my app.js
Instead of this:
store.dispatch('checkAuth');
const app = new Vue({
el: '#app',
store ,
router
});
I did this:
const VueInstance = ()=>{
new Vue({
el: '#app',
store ,
router
});
}
store.dispatch('checkAuth').then(()=>{
VueInstance();
}).catch(()=>{
VueInstance();
});
Now it is working because the vuex action checkAuth returns a promise, so I needed to wait until it is completed, but, in case the action returns an error, because in the first load the user is not logged in, I must add a catch, because th Vue should be created whether the user is logged in or not. If someone have a better solution let me know. Thank you.
After authorization, I write the user type to the state, based on this type, I want to show / hide some routes.
src/store/index.js:
import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
import user from "./modules/user";
Vue.use(Vuex);
const store = new Vuex.Store({
modules: { user },
getters
});
export default store;
src/store/getters.js:
const getters = {
token: state => state.user.token,
name: state => state.user.name,
type: state => state.user.type
};
export default getters;
src/router/index.js:
import Vue from "vue";
import Router from "vue-router";
import Layout from "#/layout";
Vue.use(Router);
export const constantRoutes = [
{
path: "/login",
component: () => import("#/views/Login"),
hidden: true
},
{
path: "/",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
name: "Dashboard",
component: () => import("#/views/Dashboard"),
meta: { title: "routes.dashboard", icon: "el-icon-odometer" }
}
]
},
{
path: "/providers",
component: Layout,
redirect: "/providers/list",
name: "Providers",
meta: { title: "routes.providers", icon: "el-icon-suitcase-1" },
children: [
{
path: 'list',
name: "List",
component: () => import("#/views/providers/ProvidersList"),
meta: { title: "routes.providersList", icon: "el-icon-document" }
}
]
}
];
const createRouter = () =>
new Router({
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
});
const router = createRouter();
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher;
}
export default router;
Authorization control in a separate file src/permission.js:
import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { getToken } from "#/utils/auth";
import getPageTitle from "#/utils/get-page-title";
NProgress.configure({ showSpinner: false });
const whiteList = ["/login"];
router.beforeEach(async (to, from, next) => {
NProgress.start();
document.title = getPageTitle(to.meta.title);
const hasToken = getToken();
if (hasToken) {
if (to.path === "/login") {
next({ path: "/" });
NProgress.done();
} else {
const hasGetUserInfo = store.getters.name;
if (hasGetUserInfo) {
next();
} else {
try {
await store.dispatch("user/getInfo");
next();
} catch (error) {
await store.dispatch("user/resetToken");
Message.error(error || "Has Error");
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
});
router.afterEach(() => {
NProgress.done();
});
As you can see all the code is a collection of copy-paste solutions found somewhere and now I'm completely stuck. How can I hide and deny access to certain routes for users with different state.user.type?
Converting my comment to answer.
Perhaps it will be easier (for you) to use an existing (and tested) solution - something like Vue-ACL or even more advanced.
I have created login page. router intercepting the request and validates the user is authenticated or not . store is maintaining the user is logged in or not.
while debugging i am getting in auth.js
"store is not defined"
I also tried relative path instead of # in imports.
router code snippet
import auth from '#/services/auth';
...
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!auth.loggedIn()) {
next({
path: '/login',
});
} else {
next();
}
} else {
next();
}
});
auth.js is like service which will interact with store and maintain state .
import Vue from 'vue';
import axios from 'axios';
import VueAxios from 'vue-axios';
import store from '#/store/UserStore';
Vue.use(VueAxios, axios);
export default {
login(credentials) {
return new Promise((resolve, reject) => {
Vue.axios.post('/api/authenticate', credentials).then((response) => {
localStorage.setItem('token', response.body.token);
store.commit('LOGIN_USER');
resolve();
}).catch((error) => {
store.commit('LOGOUT_USER');
reject(error);
});
});
},
isUserLoggedIn() {
return store.isUserLoggedIn();
},
};
here is my store to
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
state: {
isLogged: !localStorage.getItem('token'),
user: {},
},
actions: {
},
mutations: {
/* eslint-disable no-param-reassign */
LOGIN_USER(state) {
state.isLogged = true;
},
/* eslint-disable no-param-reassign */
LOGOUT_USER(state) {
state.isLogged = false;
},
},
getters: {
isUserLoggedIn: state => state.isLogged,
},
modules: {
},
});
change export type in UserStore like this:
line
export default new Vuex.Store({
replace with
export const store = new Vuex.Store({