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
Related
Before accessing any page, except login and register; I want to authenticate the user with Navigation Guards.
Following you can see my code for the vue-router. The "here" gets logged, but the navigation is not cancelled in the line afterwards. It is still possible that if the user is not authenticated that he can access the /me-route
my router-file:
import { createRouter, createWebHistory } from "vue-router";
import axios from "axios";
import HomeView from "../views/HomeView.vue";
import RegisterView from "../views/RegisterView.vue";
import LoginView from "../views/LoginView.vue";
import MeHomeView from "../views/MeHomeView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/register",
name: "register",
component: RegisterView,
},
{
path: "/login",
name: "login",
component: LoginView,
},
{
path: "/me",
name: "me",
component: MeHomeView,
},
],
});
router.beforeEach((to, from) => {
if(to.name !== 'login' && to.name !== 'register') {
console.log(to.name);
axios.post("http://localhost:4000/authenticate/", {accessToken: localStorage.getItem("accessToken")})
.then(message => {
console.log(message.data.code);
if(message.data.code === 200) {
} else {
console.log("here");
return false;
}
})
.catch(error => {
console.log(error);
return false;
})
}
})
export default router;
Navigation guards support promises in Vue Router 4. The problem is that promise chain is broken, and return false doesn't affect anything. As a rule of thumb, each promise needs to be chained.
It should be:
return axios.post(...)
The same function can be written in more readable and less error-prone way with async..await.
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 ?
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
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
}
}
I'm trying to create a menu tree with vue-router by ajax request,but the $mount function was called before the ajax request responsed, so the router in the Vue instance always null.
Is there any good solution here?
Here is my code (index.js):
import Vue from 'vue';
import Element from 'element-ui';
import entry from './App.vue';
import VueRouter from 'vue-router';
import VueResource from 'vue-resource';
import Vuex from 'vuex'
import configRouter from './route.config';
import SideNav from './components/side-nav';
import Css from './assets/styles/common.css';
import bus from './event-bus';
import dynamicRouterConfig from './dynamic.router';
Vue.use(VueRouter);
Vue.use(Element);
Vue.use(VueResource);
Vue.use(Vuex);
Vue.http.interceptors.push((request, next) => {
bus.$emit('toggleLoading');
next(() => {
bus.$emit('toggleLoading');
})
})
Vue.component('side-nav', SideNav);
app = new Vue({
afterMounted(){
console.info(123);
},
render: h => h(entry),
router: configRouter
});
app.$mount('#app');
route.config.js:
import navConfig from './nav.config';
import dynamicRouterConfig from './dynamic.router';
let route = [{
path: '/',
redirect: '/quickstart',
component: require('./pages/component.vue'),
children: []
}];
const registerRoute = (config) => {
//require(`./docs/zh-cn${page.path}.md`)
//require(`./docs/home.md`)
function addRoute(page) {
if (page.show == false) {
return false;
}
let component = page.path === '/changelog' ? require('./pages/changelog.vue') : require(`./views/alert.vue`);
if (page.path === '/edit') {
component = require('./views/edit.vue');
}
let com = component.default || component;
let child = {
path: page.path.slice(1),
meta: {
title: page.title || page.name,
description: page.description
},
component: com
};
route[0].children.push(child);
}
//if (config && config.length>0) {
config.map(nav => {
if (nav.groups) {
nav.groups.map(group => {
group.list.map(page => {
addRoute(page);
});
});
} else if (nav.children) {
nav.children.map(page => {
addRoute(page);
});
} else {
addRoute(nav);
}
});
//}
return { route, navs: config };
};
const myroute = registerRoute(navConfig);
let guideRoute = {
path: '/guide',
name: 'Guide',
redirect: '/guide/design',
component: require('./pages/guide.vue'),
children: [{
path: 'design',
name: 'Design',
component: require('./pages/design.vue')
}, {
path: 'nav',
name: 'Navigation',
component: require('./pages/nav.vue')
}]
};
let resourceRoute = {
path: '/resource',
name: 'Resource',
component: require('./pages/resource.vue')
};
let indexRoute = {
path: '/',
name: 'Home',
component: require('./pages/index.vue')
};
let dynaRoute = registerRoute(dynamicRouterConfig).route;
myroute.route = myroute.route.concat([indexRoute, guideRoute, resourceRoute]);
myroute.route.push({
path: '*',
component: require('./docs/home.md')
});
export const navs = myroute.navs;
export default myroute.route;
And dynamic.router.js:
module.exports = [
{
"name": "Edit",
"path": "/edit"
}
]
Now the static route config is woking fine ,but how can I load data from server side by ajax request in the route.config.js instead of static data.
Waiting for some async request at page render is fine, just set empty initial values in the data section of component like:
data() {
someStr: '',
someList: []
}
and make sure you handle the empty values well without undefined errors trying to read things like someList[0].foo.
Then when the request comes back, set the initially empty values to those real data you get from the request.
Giving the user some visual indicate that you're fetching the data would be a good practice. I've found v-loading in element-ui useful for that.