Accessing Vuex state when defining Vue-Router routes

105,915

Solution 1

As suggested here, what you can do is to export your store from the file it is in and import it in the routes.js. It will be something like following:

You have one store.js:

import Vuex from 'vuex'

//init store
const store = new Vuex.Store({
    state: {
        globalError: '',
        user: {
            authenticated: false
        }
     },
     mutations: {
         setGlobalError (state, error) {
             state.globalError = error
         }
     }
})

export default store

Now in routes.js, you can have:

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from ./store.js

Vue.use(VueRouter)

//define routes
const routes = [
    { path: '/home', name: 'Home', component: Home },
    { path: '/login', name: 'Login', component: Login },
    { path: '/secret', name: 'Secret', component: SecretPage, meta: { requiresLogin: true }
]

Router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresLogin) && ???) {
        // You can use store variable here to access globalError or commit mutation 
        next("/Login")
    } else {
        next()
    }
})

In main.js also you can import store:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import store from './store.js'

//init app
const app = new Vue({
    router: Router,
    store,
    template: '<app></app>',
    components: { App }
}).$mount('#app')

Solution 2

You may use router.app to access the root Vue instance the router was injected into, then access store regularly via router.app.$store.

const router = new Router({
    routes,
})

router.beforeEach((to, from, next) => {
    // access store via `router.app.$store` here.
    if (router.app.$store.getters('user')) next();
    else next({ name: 'login' });
})

Here is the API Reference.

Vue 3

The router.app is removed in Vue 3, but you can still add it when using the router as explained in the migration guide:

app.use(router)
router.app = app

Solution 3

I ended up moving the store out of main.js and into store/index.js, and importing it into the router.js file:

import store from './store'

//routes

const routes = [
    { path: '/home', name: 'Home', component: Home },
    { path: '/login', name: 'Login', component: Login },
    { path: '/secret', name: 'Secret', component: SecretPage, meta: { requiresLogin: true }
]    

//guard clause
Router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresLogin) && store.state.user.authenticated == false) {
        store.commit("setGlobalError", "You need to log in before you can perform this action.")
        next("/Login")
    } else {
        next()
    }
})

Solution 4

Managing your location state separate from the rest of your application state can make things like this harder than they maybe need to be. After dealing with similar problems in both Redux and Vuex, I started managing my location state inside my Vuex store, using a router module. You might want to think about using that approach.

In your specific case, you could watch for when the location changes within the Vuex store itself, and dispatch the appropriate "redirect" action, like this:

dispatch("router/push", {path: "/login"})

It's easier than you might think to manage the location state as a Vuex module. You can use mine as a starting point if you want to try it out:

https://github.com/geekytime/vuex-router

Solution 5

I found that the store was not available to me in router.js when using the guard router.beforeEach, however by changing the guard to router.beforeResolve, then the store was available.

I also found that by awaiting the import of the store in the guard router.beforeEach, I was then able to successfully use router.beforeEach. I provide an example of that below the router.beforeResolve code.

So to keep my example simular to the OP's question the following is how it would have worked for me. I am using vue-router 3.0.2 and vuex 3.1.0.

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store';  //or use a full path to ./store 

Vue.use(VueRouter)

//define routes
const routes = [
    { path: '/home', name: 'Home', component: Home },
    { path: '/login', name: 'Login', component: Login },
    { path: '/secret', name: 'Secret', component: SecretPage, meta: { requiresLogin: true }
]

const router = new VueRouter({
   routes  //es6
 })

router.beforeResolve((to, from, next) => {
    const user = store.state.user.user;  //store with namespaced  modules
    if (to.matched.some(record => record.meta.requiresLogin) && user.isLoggedIn) {
       next() //proceed to the route
    } else next("/login")  //redirect to login

})

export default router;

I also found that I could get router.beforeEach to work by await-ing the loading of the store in the beforeEach guard.

router.beforeEach(async (to, from, next) => {
  const store = await import('@/store');  //await the store 
  const user = store.state.user.user;  //store with namespaced modules
  if (to.matched.some(record => record.meta.requiresLogin) && user.isLoggedIn) {
  ....  //and continue as above
});
Share:
105,915

Related videos on Youtube

Ege Ersoz
Author by

Ege Ersoz

Updated on November 17, 2021

Comments

  • Ege Ersoz
    Ege Ersoz over 2 years

    I have the following Vuex store (main.js):

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    //init store
    const store = new Vuex.Store({
        state: {
            globalError: '',
            user: {
                authenticated: false
            }
         },
         mutations: {
             setGlobalError (state, error) {
                 state.globalError = error
             }
         }
    })
    
    //init app
    const app = new Vue({
        router: Router,
        store,
        template: '<app></app>',
        components: { App }
    }).$mount('#app')
    

    I also have the following routes defined for Vue Router (routes.js):

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    //define routes
    const routes = [
        { path: '/home', name: 'Home', component: Home },
        { path: '/login', name: 'Login', component: Login },
        { path: '/secret', name: 'Secret', component: SecretPage, meta: { requiresLogin: true }
    ]
    

    I'm trying to make it so that, if Vuex stores the user object, and it has the authenticated property set to false, is has the router redirect the user to the login page.

    I have this:

    Router.beforeEach((to, from, next) => {
        if (to.matched.some(record => record.meta.requiresLogin) && ???) {
            // set Vuex state's globalError, then redirect
            next("/Login")
        } else {
            next()
        }
    })
    

    The problem is I don't know how to access the Vuex store's user object from inside the beforeEach function.

    I know that I can have the router guard logic inside components using BeforeRouteEnter, but that would clutter up each component. I want to define it centrally at the router level instead.

    • Brian Cannard
      Brian Cannard over 2 years
      It seems that none of the answers follow best OOP practice re: composition and encapsulation: either depend on global "singletons", or patch everything in non-declarative place where routes are instantiated, rather defined. Does anyone know what is the Vue 3 solution in 2022?..
    • Brian Cannard
      Brian Cannard about 2 years
      I ended up wrapping protected pages in a Vue component which dispatches an action on its mount. Most of routes are app routes, you can add a meta flag to the public routes so it'll skip the check for those. Many apps already have the App.vue, a perfect place for those checks & redirect if access token isn't there.
  • Ege Ersoz
    Ege Ersoz about 7 years
    Thanks Saurabh, that's exactly what I ended up doing. I'll select your answer, as it's more extensive than mine. :)
  • Sid
    Sid about 6 years
    Hi, I want to access store in components(Home, Login, Secret) in above example. How can I do this? My question: stackoverflow.com/questions/49959675/…
  • chovy
    chovy almost 6 years
    I get 'store is not defined' when doing this.
  • Daniel Twigg
    Daniel Twigg about 5 years
    This would cause a problem if you need to import the router into the store due to circular dependency issues.
  • nVitius
    nVitius about 5 years
    How does this not create a new Store every time you import it?
  • Orkhan Alikhanov
    Orkhan Alikhanov over 4 years
    @DanielTwigg No, it wouldn't. Circular dependency resolution is resolved by webpack
  • vintprox
    vintprox over 4 years
    @nVitius Since object is all same, it would be reused. At least that's how webpack import works.
  • scipilot
    scipilot over 4 years
    @vintproykt that's true, 99.9% of the time. I've seen webpack create a duplicate instance once when it thought two relative import paths to the same module were unequal. Took ages to debug. I solved it with DI instead.
  • scipilot
    scipilot over 4 years
    Interesting use of the watcher - I didn't know you could watch cookies. But the question requires it to only work from /some/ pages. Your watcher function would also need to check that metadata and not redirect if the user was on a public page.
  • Jay Bienvenu
    Jay Bienvenu over 4 years
    This is a poor solution to a dependency-injection problem. The router is now coupled to the store. What if the router is created in a framework and the store isn't created but in an application?
  • trainoasis
    trainoasis about 4 years
    i get an error about Vue.use(Vuex) not being called in store before creating a store instance.
  • devman
    devman almost 4 years
    your method of awaiting the store is very nice. i used it to solve a problem like this and it works great!
  • Andi
    Andi over 3 years
    but this is an In-Component Guard, so you cannot define it globally for all routes
  • el.nicko
    el.nicko over 3 years
    @andi sure. I'd be interested to see your perspective on how this could be a functional limitation for what the op is asking? Did I overlook something?
  • Andi
    Andi over 3 years
    @el.niko In his last paragraph he says that he knows about beforeRouteEnter but he wants to "define it centrally"
  • el.nicko
    el.nicko over 3 years
    @andi, thank you very much for pointing that out. AFAICS the OP didn't limit the solution to a static one, however your remark is valid. I appear to have laid out the ground work of the solution but came short on explaining my motivation and sln itself to the end. The beauty of the non-static approach is that it can be injected more flexibly and thus is better compatible to non-trivial applications (e.g. multiple store instances etc). I edited my answer.
  • Caleb Jay
    Caleb Jay about 3 years
    Correct me if i'm wrong, but this would merely await a dynamic webpack import, not the store actually "loading" or the vue code that binds the store to the vue app object, correct? So this is kinda similar to doing something like setTimeout(1) to "await" a pass through the loop?
  • Tom Carchrae
    Tom Carchrae almost 3 years
    just a note of caution; in my project at least, $store is not injected on the first page render (first call to beforeEach). however, if you use beforeResolve then $store will be injected on the first render.
  • KenBuckley
    KenBuckley almost 3 years
    @CalebJay Thanks for your observation Caleb. Do you have an idea why just even a simple await would change the beforeEach functionality to where it works as expected versus its default position as not working as expected? I'd love to get an insight as to why.
  • John Smith
    John Smith almost 3 years
    Ambiguous response. Explain how you actually implemented to solve the question.
  • Brian Cannard
    Brian Cannard over 2 years
    Are Vuex modules also singletones? In Elm, the magic of nested fractal sub-applications was assuming zero singletones anywhere in the well-organized microfrontend consuming microservices architecture.
  • Brian Cannard
    Brian Cannard over 2 years
    Anyway, it's a bad OOP composition "bad practice".
  • DWR
    DWR almost 2 years
    Is there any point of further injection of the store (e.g. using useStore()), if the store is a global?