Dynamic router VueJS - vue.js

I'm trying to create routes dynamically but unfortunately I'm not succeeding and it's not possible with Vuejs 3
import { createRouter, createWebHistory } from "vue-router";
import TopMenu from "../layouts/top-menu/Main.vue";
import hosts from '#/utils/hosts';
import Login from "../views/admin/login/Main.vue";
import Home from "../views/admin/home/Main.vue";
import Sair from "../views/admin/sair/Main.vue";
import { listaPaginas } from "#/composable/arthysis/paginas";
let routes = [];
var lstRoutes = [];
async function loadPage() {
var { statuscode, message, data } = await searchPage();
lstRoutes.push({
path: `${hosts.app}` + "/home",
name: "admManHome",
component: Home,
});
for (var i = 0; i < data.length; i++) {
lstRoutes.push({
path: `${hosts.app}/${data[i].path}`,
name: data[i].name_page,
component: () => import('../views/'+ data[i].path +'/'+ data[i].name_page +'.vue')
});
}
}
await loadPage();
routes = [
{
path: `${hosts.app}/`,
name: "admManLogin",
component: Login,
},
{
component: TopMenu,
children: lstRoutes
}
];
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
return savedPosition || { left: 0, top: 0 };
},
});
export default router;
it is giving this error
vue-router.mjs:1486 Uncaught TypeError: Cannot read properties of undefined (reading 'forEach')
at createRouterMatcher (vue-router.mjs:1486:12)
at createRouter (vue-router.mjs:2942:21)
at index.js?t=1675540466219:137:16
I tried to create a function to import the component but it also gives the same error

main.js
import { createApp, ref } from "vue";
import { createPinia, defineStore } from "pinia";
import App from "./App.vue";
import router from "./router";
import globalComponents from "./global-components";
import utils from "./utils";
import "./assets/css/app.css";
import hosts from '#/utils/hosts';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
const app = createApp(App).use(router).use(pinia);
globalComponents(app);
utils(app);
app.mount("#app");
router.beforeEach(async (to, from, next) => {
if ((localStorage.getItem("token") == undefined || localStorage.getItem("token") == null) && to.name != 'admManLogin') {
localStorage.removeItem("token");
localStorage.removeItem("dataExptoken");
localStorage.removeItem("loja");
next({ path: `${hosts.app}/` })
}
if (localStorage.getItem("dataExptoken") != undefined && Date.now() > localStorage.getItem("dataExptoken")) {
localStorage.removeItem("token");
localStorage.removeItem("dataExptoken");
localStorage.removeItem("loja");
next({ path: `${hosts.app}/` })
}
next()
})

router
....
async function searchRoutes() {
var { statuscode, message, data } = await searchPage();
let lstRoutes = [];
for (var i = 0; i < data.length; i++) {
let dsPath = `${hosts.app}/${data[i].path}`
if (data[i].parameter != '' && data[i].parameter != null) {
dsPath += "/:"+data[i].parameter+"?";
}
lstRoutes.push({
path: dsPath,
name: data[i].name_page ,
component: () => import('../views/'+ data[i].path +'/'+ data[i].name_page +'.vue')
});
}
return lstRoutes;
}
async function createRouterInstance() {
let lstRoute = [];
lstRoute.push({
path: `${hosts.app}` + "/home",
name: "admManHome",
component: Home,
});
let respRotas = await searchRoutes();
let routes = [
{
path: `${hosts.app}/`,
name: "admManLogin",
component: Login,
},
{
component: TopMenu,
children: lstRoute
}
];
const router1 = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
return savedPosition || { left: 0, top: 0 };
},
});
return router1;
}
const router = await createRouterInstance();
vue-router.mjs:798 Uncaught (in promise) Error: No match for
{"name":"listUsers","params":{}}
at createRouterError (vue-router.mjs:798:23)
at Object.resolve (vue-router.mjs:1414:23)
at Object.resolve (vue-router.mjs:3045:38)
at Main.vue:44:94
at renderList (runtime-core.esm-bundler.js:2894:22)
at Main.vue:54:22
at renderList (runtime-core.esm-bundler.js:2894:22)
at Main.vue:58:18
at renderList (runtime-core.esm-bundler.js:2894:22)
at Proxy._sfc_render (Main.vue:61:14)

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

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

How to use router in vue composition api?

I defined a route in vue:
/users/:userId
Which point to UserComponent:
<template>
<div>{{username}}</div>
</template>
and I use computed from #vue/composition-api to get the data.
the problem is when the route change to another userId, by navigate to another user, the user in the html template not changed as what I expected. also it doesn't do redirect when the the user is not in the list.
So what I can do to fix that?
here is my code:
<template>
<div>{{username}}</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, getCurrentInstance } from '#vue/composition-api';
export const useUsername = ({ user }) => {
return { username: user.name };
};
export default defineComponent({
setup(props, { root }) {
const vm = getCurrentInstance();
const userToLoad = computed(() => root.$route.params.userId);
const listOfUsers = [
{ userId: 1, name: 'user1' },
{ userId: 2, name: 'user2' },
];
const user = listOfUsers.find((u) => u.userId === +userToLoad.value);
if (!user) {
return root.$router.push('/404');
}
const { username } = useUsername({ user });
return { username };
},
});
</script>
You can just do this:
import { useRoute } from 'vue-router';
export default {
setup() {
const route = useRoute();
// Now you can access params like:
console.log(route.params.id);
}
};
From the vue-router documentation:
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
function pushWithQuery(query) {
if (!user) {
router.push({
name: '404',
query: {
...route.query
}
})
}
}
}
}
You can pass the parameters as props to your components. Props are reactive by default.
This is how the route configuration could look like:
{
path: '/users/:userId',
name: Users,
component: YourComponent
},
You can then use the props in your component with watchEffect()
<template>
<div>{{username}}</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, getCurrentInstance, watchEffect } from '#vue/composition-api';
export const useUsername = ({ user }) => {
return { username: user.name };
};
export default defineComponent({
props: {userId: {type: String, required: true },
setup(props, { root }) {
const vm = getCurrentInstance();
const user = ref()
const userToLoad = computed(() => props.userId);
const listOfUsers = [
{ userId: 1, name: 'user1' },
{ userId: 2, name: 'user2' },
];
watchEffect(() => user.value = listOfUsers.find((u) => u.userId === +userToLoad.value))
if (!user) {
return root.$router.push('/404');
}
const { username } = useUsername({ user });
return { username };
},
});
</script>
watchEffect() will run immediately when defined and when reactive dependencies.change
A had the same problem. I use vue 2 and #vue/composition-api
My resolution:
Created: src/router/migrateRouterVue3.js
import { reactive } from '#vue/composition-api';
import router from './index';
const currentRoute = reactive({
...router.currentRoute,
});
router.beforeEach((to, from, next) => {
Object.keys(to).forEach(key => {
currentRoute[key] = to[key];
});
next();
});
// eslint-disable-next-line import/prefer-default-export
export function useRoute() {
return currentRoute;
}
after that, I can usage:
// import { useRoute } from 'vue-router';
import { useRoute } from '#/router/migrateRouterVue3';
Resolution for you:
// replace:
// const userToLoad = computed(() => root.$route.params.userId);
// to:
import { useRoute } from '#/router/migrateRouterVue3';
//...
const route = useRoute();
const userToLoad = computed(() => route.params.userId);
function useRoute() {
const vm = getCurrentInstance()
if (!vm) throw new Error('must be called in setup')
return vm.proxy.$route
}
https://github.com/vuejs/composition-api/issues/630
The following useRoute hook will make route reactive so that it's doable:
const route = useRoute();
const fooId = computed(()=>route.params.fooId);
let currentRoute = null;
export const useRoute = () => {
const self = getCurrentInstance();
const router = self.proxy.$router;
if (!currentRoute) {
const route = { ...self.proxy.$route };
const routeRef = shallowRef(route);
const computedRoute = {};
for (const key of Object.keys(routeRef.value)) {
computedRoute[key] = computed(() => routeRef.value[key]);
}
router.afterEach((to) => {
routeRef.value = to;
});
currentRoute = reactive(computedRoute);
}
return currentRoute;
};
The vue2-helpers package provides a useRoute function you can use in Vue 2.7 (and 2.6, 2.5 also).
Installation
# Vue 2.7
$ npm install vue2-helpers#2
# Vue 2.5 and 2.6
$ npm install vue2-helpers#1
Usage
import { useRoute } from 'vue2-helpers/vue-router';
const route = useRoute();
const id: string | undefined = route.params.id;
const { proxy } = getCurrentInstance();
then use proxy to access $router or $route
Add please this code: watchEffect(() => userToLoad);

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
}
}