8

In my components I've been using:

this.$router.push({ name: 'home', params: { id: this.searchText }});

To change route. I've now moved a method into my Vuex actions, and of course this.$router no longer works. Nor does Vue.router. So, how do I call router methods from the Vuex state, please?

daninthemix
  • 2,542
  • 8
  • 29
  • 40
  • 1
    Duplicate of http://stackoverflow.com/questions/40736799/how-to-navigate-route-from-vuex-actions – Saurabh Nov 23 '16 at 16:15

5 Answers5

15

I'm assuming vuex-router-sync won't help here as you need the router instance.

Therefore although this doesn't feel ideal you could set the instance as a global within webpack, i.e.

global.router = new VueRouter({
  routes
})

const app = new Vue({
  router
  ...

now you should be able to: router.push({ name: 'home', params: { id: 1234 }}) from anywhere within your app


As an alternative if you don't like the idea of the above you could return a Promise from your action. Then if the action completes successfully I assume it calls a mutation or something and you can resolve the Promise. However if it fails and whatever condition the redirect needs is hit you reject the Promise.

This way you can move the routers redirect into a component that simply catches the rejected Promise and fires the vue-router push, i.e.

# vuex
actions: {
  foo: ({ commit }, payload) =>
    new Promise((resolve, reject) => {
      if (payload.title) {
        commit('updateTitle', payload.title)
        resolve()
      } else {
        reject()
      }
    })

# component
methods: {
  updateFoo () {
    this.$store.dispatch('foo', {})
      .then(response => { // success })
      .catch(response => {
        // fail
        this.$router.push({ name: 'home', params: { id: 1234 }})
      })
GuyC
  • 6,494
  • 1
  • 31
  • 44
  • Your first suggestion works - thanks. Why is it a bad idea to attach router to global? Similarly, I need global access to Vue-Resource - for some reason importing Vue and using Vue.http works fine (but is it optimal?) whereever I need Vue-Resource. Should I attach Vue-Resource to global as well? Regarding your second point, I will look into that (I don't much understand promises) – daninthemix Nov 23 '16 at 16:02
  • I'm not really sure that it is a bad idea, it just feels a bit wrong - like this should be something built into **vuex-router-sync**. I've elaborated on the second idea just in case as I really like Promises! On your point with vue-resource, yeah that's fine - `Vue.http` is intended to be used this way, no need to make that global. – GuyC Nov 23 '16 at 16:10
  • I'm having trouble with my Promise syntax - would you mind taking a second to look? http://jsbin.com/webezelaki/edit?js – daninthemix Nov 23 '16 at 17:04
1

I a situation, I find myself to use .go instead of .push.

Sorry, no explanation about why, but in my case it worked. I leave this for future Googlers like me.

realtebo
  • 23,922
  • 37
  • 112
  • 189
0

I believe rootState.router will be available in your actions, assuming you passed router as an option in your main Vue constructor.

As GuyC mentioned, I was also thinking you may be better off returning a promise from your action and routing after it resolves. In simple terms: dispatch(YOUR_ACTION).then(router.push()).

Chris
  • 1,419
  • 14
  • 17
  • Yes, I did ultimately go with the Promise approach that GuyC mentioned. How did you learn / how are we expected to know about rootState? Haven't seen that documented anywhere. – daninthemix Nov 24 '16 at 08:13
  • Yeah it probably isn't given the emphasis in the docs it should be. It's sort of mentioned in the Vuex [actions](https://vuex.vuejs.org/en/actions.html) documentation and otherwise only mentioned in the [modules](https://vuex.vuejs.org/en/modules.html) documentation - it definitely doesn't jump out at you. – Chris Nov 24 '16 at 14:56
  • There is no `router` in the rootState by default. Injecting it manually would also be overkill, as the router instance is mostly immutable, except for the `route` itself, which is part of the state if using vuex-router-sync. – Tomas Buteler Aug 15 '17 at 08:04
0
    state: {
        anyObj: {}, // Just filler
        _router: null // place holder for router ref
    },
    mutations: {
        /***
        * All stores that have this mutation will run it
        *
        *  You can call this in App mount, eg...

        *  mounted () {
        *    let vm = this
        *    vm.$store.commit('setRouter', vm.$router)
        *  }
        *
        setRouter (state, routerRef) {
            state._router = routerRef
        }
    },
    actions: {
        /***
        * You can then use the router like this
        * ---
        someAction ({ state }) {
            if (state._router) {
                state._router.push('/somewhere_else')
            } else {
                console.log('You forgot to set the router silly')
            }
        }
    }
}

user7296390
  • 160
  • 7
0

Update

After I published this answer I noticed that defining it the way I presented Typescript stopped detecting fields of state. I assume that's because I used any as a type. I probably could manually define the type, but it sounds like repeating yourself to me. That's way for now I ended up with a function instead of extending a class (I would be glad for letting me know some other solution if someone knows it).

import { Store } from 'vuex'
import VueRouter from 'vue-router'
// ...

export default (router: VueRouter) => {
  return new Store({
  // router = Vue.observable(router) // You can either do that...
  super({
    state: {
      // router // ... or add `router` to `store` if You need it to be reactive.
      // ...
    },
    // ...
  })
}
import Vue from 'vue'
import App from './App.vue'
import createStore from './store'
// ...

new Vue({
  router,
  store: createStore(router),
  render: createElement => createElement(App)
}).$mount('#app')

Initial answer content

I personally just made a wrapper for a typical Store class.

import { Store } from 'vuex'
import VueRouter from 'vue-router'
// ...

export default class extends Store<any> {
  constructor (router: VueRouter) {
    // router = Vue.observable(router) // You can either do that...
    super({
      state: {
        // router // ... or add `router` to `store` if You need it to be reactive.
        // ...
      },
      // ...
    })
  }
}

If You need $route You can just use router.currentRoute. Just remember You rather need router reactive if You want Your getters with router.currentRoute to work as expected.

And in "main.ts" (or ".js") I just use it with new Store(router).

import Vue from 'vue'
import App from './App.vue'
import Store from './store'
// ...

new Vue({
  router,
  store: new Store(router),
  render: createElement => createElement(App)
}).$mount('#app')
kcpr
  • 1,055
  • 1
  • 12
  • 28