How to navigate using vue router from Vuex actions - javascript

I am creating a web app with Vue 2.x and Vuex 2.x. I am fetching some information from a remote location via an http call, I want that if that call fails I should redirect to some other page.
GET_PETS: (state) => {
return $http.get('pets/').then((response)=>{
state.commit('SET_PETS', response.data)
})
},
error => {this.$router.push({path:"/"}) }
)
}
But this.$router.push({path:"/"}) gives me following error.
Uncaught (in promise) TypeError: Cannot read property 'push' of undefined
How can this be achieved.
Simulated JsFiddle: here

import router from './router'
and use router.push
Simple like that.

This example may help you.
main.js
import Vue from "vue";
import VueRouter from "vue-router";
...
Vue.use(VueRouter);
export const router = new VueRouter({
mode: 'hash',
base: "./",
routes: [
{ path: "/", component: welcome},
{ path: "/welcome", component: welcome},
]
})
actions.js
import {router} from "../main.js"
export const someAction = ({commit}) => {
router.push("/welcome");
}

It looks like you aren't injecting your router into your app, hence it being 'undefined'
In previous versions of vue-router you would: Vue.use(VueRouter), with 2.0 you can inject the router into the app like below:
const routes = [
{ path: '/foo', component: Foo },
]
const router = new VueRouter({
routes
})
const app = new Vue({
router // inject the router
}).$mount('#app')
this should then make it available as this.$router throughout the app
Following answering a related question: How to use Vue Router from Vuex state? it seems that Vuex won't receive the router instance at this.$router. Therefore two methods were suggested to provide access to the router instance.
The first is more direct which involves setting a webpack global to the instance.
The second involves using Promises with your vuex action that would allow your components to utilise their reference to the router instance following the actions Promise resolving / rejecting.

INITIAL ANSWER
In main.js (the one, where we "install" all modules and create Vue instance, i.e. src/main.js):
const vm = new Vue({
el: '#app',
router,
store,
apolloProvider,
components: { App },
template: '<App/>'
})
export { vm }
This is my example, but in our case the most important here is const vm and router
In your store:
import { vm } from '#/main'
yourMutation (state, someRouteName) {
vm.$router.push({name: someRouteName})
}
P.S. Using import { vm } from '#/main' we can access anything we need in Vuex, for example vm.$root which is needed by some components of bootstrap-vue.
P.P.S. It seems we can use vm just when everything is loaded. In other words we can not use vm inside someMutation in case, if we call someMutation inside mounted(), because mounted() comes/occurs before vm is created.
NEW ANSWER
Constantin's answer (the accepted one) is better than mine, so just want to show for novice how to implement it.
Inside core dir (inside /src in my case), next to App.vue, main.js and others I have router.js with the content:
import Vue from 'vue'
import Router from 'vue-router'
// Traditional loading
import Home from '#/components/pages/Home/TheHome'
// Lazy loading (lazy-loaded when the route is visited)
const Page404 = () => import(/* webpackChunkName: "Page404" */ '#/components/pages/404)
const Page503 = () => import(/* webpackChunkName: "Page503" */ '#/components/pages/503)
Vue.use(Router)
const router = new Router({
mode: 'hash',
base: process.env.BASE_URL,
linkExactActiveClass: 'active',
routes: [
{
path: '*',
name: 'Page404',
component: Page404
},
{
path: '*',
name: 'Page503',
component: Page503
},
{
path: '/',
name: 'Home',
component: Home
},
// Other routes
{....},
{....}
]
})
// Global place, if you need do anything before you enter to a new route.
router.beforeEach(async (to, from, next) => {
next()
})
export default router
Import our router to main.js:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
const vm = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
export { vm }
Finally, inside your component, or Vuex or anywhere else import router from './router' and do whatever you need, such as router.push(...)

I didn't like keeping my app's location state separate from the rest of my app state in the Store, and having to manage both a Router and a Store, so I created a Vuex module that manages the location state inside the Store.
Now I can navigate by dispatching actions, just like any other state change:
dispatch("router/push", {path: "/error"})
This has the added benefit of making things like animated page transitions easier to handle.
It's not hard to roll your own router module, but you can also try mine if you want to:
https://github.com/geekytime/vuex-router

You can simply import route from router directory like this:
import router from '#/router'
router.push({name: 'Home'})
this # symbol replaces the path to the src directory

Related

Why does export const work but export function does not?

I am trying to ensure that the same instance of vue-router is available in multiple places in my app.
The vue.router.js file exports the router like this:
export function createVueRouter() {
return createRouter({
history: createWebHistory('/')
routes: [//array of routes]
});
}
In my main app.js file (or main.js if you prefer) it is used like this:
import {createVueRouter} from './router/vue/vue.router.js';
const router = createVueRouter();
app.use(router);
The Vue app now has access to the vue-router. I now want the same vue-router to be available in an independent JS file. But the following does not work:
resusable.js
import {createVueRouter} from './router/vue/vue.router.js';
const router = createVueRouter();
//....
if (isLoggedInBasic !== true) {
return await router.push({name: "Signin"})
}
It creates a new instance of the vue-router and the router.push does not take the user to the Signin route (but the URL in the address bar does change).
If I change vue.router.js export to the following, it works fine all the files with the same router instance being shared between them.
export const VueRouter = createRouter({
history: createWebHistory('/')
routes: [//array of routes]
})
I cannot understand what is the difference and why it works when export const is used rather than just export function ()

(vue-test-utils) 'could not overwrite property $route,...'

Hi I'm testing vue project using vue-test-utils.
What I wanna do is to test router (my project is using VueRouter).
I know that if I import routes from router file or VueRouter and use it to localVue, $route and $router properties are read-only so I can't mock it.
But when I tried like
transfers.test.js
import { createLocalVue, shallowMount } from '#vue/test-utils'
// import VueRouter from 'vue-router'
// import routes from '../../router/Routes'
import ElementUI from 'element-ui'
...
const localVue = createLocalVue()
// localVue.use(VueRouter)
localVue.use(ElementUI)
localVue.use(Vuex)
localVue.component('DefaultLayout', DefaultLayout)
// const router = new VueRouter({ routes })
...
describe('Elements', () => {
const div = document.createElement('div')
div.id = 'root'
document.body.appendChild(div)
const wrapper = shallowMount(Transfers, {
localVue,
store,
mocks: {
$route: {
params: { processing: 'failed' }
}
},
// router,
attachTo: '#root'
})
afterAll(() => {
wrapper.destroy()
})
...
console.error
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value
appears.
Actually, what I truly wanna do is not to mock route but to use real router but there is another issue..
'Route with name 'something' does not exist' vue-router console.warn when using vue-test-utils
If you know the solutions for these issues, please let me know! Thank you for in advance.
I certainly solved this problem with the first answer of this question!
vue-test-utils: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value
It seems like using VueRouter in anywhere(even in non-test codes) affects mocking $route.
if (!process || process.env.NODE_ENV !== 'test') {
Vue.use(VueRouter)
}
I solved with this code in the first answer of question linked. Use this code to where VueRouter is used!

Add route to Vue.js router while app is running

I'm currently building an app and I would like to add extensions to this app. These extensions should have their own Vue components and and views (and thus also routes). I don't want to rebuild the app but instead add the new views and routes dynamically. Is there a good way to do that with Vue 2?
In the following I added the files that I hope makes this question a bit more comprehensible. The router/index.js contains the basic structure and is added to the main.js file in the regular fashion. During the load of the app.vue the new routes should be loaded and appended to the already existing ones.
router
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/test">Test</router-link>
</div>
<router-view/>
</div>
</template>
<script>
// # is an alias to /src
import TestView from '#/views/Test.vue'
export default {
name: 'Home',
components: {},
created() {
<add route to router>({
component: TestView,
name: "Test",
path: "/test"
})
}
}
</script>
I used the phrase <add route to router> to demonstrate the way I would like to add the route. After the route is added the user should be able to directly navigate to the new view using <router-link to="/test">Test</router-link>.
Any help would be appreciated.
Use addRoute to add routes at runtime. Here is the explanation for this method from the docs:
Add a new route to the router. If the route has a name and there is already an existing one with the same one, it overwrites it.
Import the router into App.vue to use it:
App.vue
<script>
import router from './router/index.js';
import TestView from '#/views/Test.vue'
export default {
created() {
router.addRoute({
component: TestView,
name: "Test",
path: "/test"
})
}
}
</script>

Vue js "store is not defined" in router.js

I want to perform a redirect, but before the redirect happens i want to perform a action using store.dispatch. Error is "store is not defined" in the console.
I have tried putting the whole line of code in a variable and check if true and if null, the error dissapears but the actions never gets called, and the debugger shows vue is jumping over the if-statement.
import Vue from 'vue'
import store from './store/index'
import Router from 'vue-router'
import Settings from './views/Settings.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/myPath',
name: 'myPathName',
component: {},
beforeEnter(to, from, next) {
//STORE is not defined
store.dispatch("path/MY_ACTION");
next({
name: "destinationPath",
})
}
}
-----------------------------------------------------------------------------
MY STORE
// STORE -> MODULES -> CONFIGURATION -> INDEX
import windowsModule from "../windows/index"
import mainDoorModule from "../maindoor/index"
import doorLeavesModule from "../doorleaves/index"
import doorModule from "../door/index"
import actions from "./actions"
import mutations from "./mutations"
export default {
namespaced: true,
modules: {
windows: windowsModule,
mainDoor: mainDoorModule,
doorLeaves: doorLeavesModule,
door: doorModule
},
state: {
configurationId: 0,
savedConfigurationsViewModel: [],
errors: {},
configurationsToSend: []
},
mutations,
actions
}
//THE ACTION I AM TRYING TO REACH INSIDE ACTIONS
// STORE -> MODULES -> CONFIGURATION -> ACTIONS
GET_DEFAULT_CONFIGURATION({ commit }) {
commit('SET_CONFIGURATION', {
//DATA
}
}
You need to install it in the main component. Then you refer to this through this.$store. Read the Vuex documentation.
export const store = new Vuex.Store({
state: {},
mutations: {},
getters: {}
})
import store from './store/index'
new Vue({
store, // <- here
el: '#app'
})
So it resolved. It was a Chrome bug saying that store is undefined, it's in webpack. Store was defined.
The problem was the mismatch between actions and mutations sending payloads that did not match types.
Thanks for all the answers, i appreciate it.

Router not working when moving code into helper file

I tried to create some organization in my Vue app, so I moved my router confirguration to a separate file:
# /_helpers/router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import MemberDetail from '../MemberDetail';
import LoginPage from '../LoginPage';
Vue.use(VueRouter)
const routes = [
{ path: '/', component: MemberDetail },
{ path: '/login', component: LoginPage },
];
export const router = new VueRouter({routes});
router.beforeEach((to, from, next) => {
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('user');
if (authRequired && !loggedIn) {
return next('/login');
}
next();
})
Then I add the exported router in my main application.js:
import Vue from 'vue/dist/vue.esm'
import App from '../components/app.vue'
import { router } from '../components/_helpers';
document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#app',
router,
render: h => h(App)
});
})
In my App.vue, I try to display the router-view:
<template>
<div class="page-container">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app'
};
</script>
<style scoped>
</style>
Unfortunataly this results in the following error:
[Vue warn]: Unknown custom element: <router-view> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I don't understand what's wrong... I'm adding the router to the new Vue call, so I think it should be available.
Thanks

Categories