Best practice to change the route (VueRouter) after a mutation (Vuex) - vuejs2

I've searched a lot, but there is no clear answer to that. Basically, what should be the best practice to automatically change a route after a mutation?
Ex: I click a button to login() -> action login that makes an http call -> mutation LOGIN_SUCCESSFUL -> I want to redirect the user to the main page $router.go()
Should I wrap the action in a Promise, and then listen to the result to call the route change from the component?
Should I do it directly from the $store?
Does vuex-router-sync helps in any way?
Thanks a lot!

The answer to this questions seems to be somewhat unclear in the Vue community.
Most people (including me) would say that the store mutation should not have any effects besides actually mutating the store. Hence, doing the route change directly in the $store should be avoided.
I have very much enjoyed going with your first suggestion: Wrapping the action in a promise, and changing the route from withing your component as soon as the promise resolves.
A third solution is to use watch in your component, in order to change the route as soon as your LOGGED_IN_USER state (or whatever you call it) has changed. While this approach allows you to keep your actions and mutations 100% clean, I found it to become messy very, very quickly.
As a result, I would suggest going the promise route.

Put an event listener on your app.vue file then emit en event by your mutation function. But I suggest you wrapping the action in a promise is good way
App.vue:
import EventBus from './eventBus';
methods: {
redirectURL(path) {
this.$router.go(path)}
},
created() {
EventBus.$on('redirect', this.redirectURL)
}
mutation:
import EventBus from './eventBus';
LOGIN_SUCCESSFUL() {
state.blabla = "blabla";
EventBus.$emit('redirect', '/dashboard')
}

As of now (mid 2018) API of Vuex supports subscriptions. Using them it is possible to be notified when a mutation is changing your store and to adjust the router on demand.
The following example is an excerpt placed in created() life-cycle hook of a Vue component. It is subscribing to mutations of store waiting for the first match of desired criteria to cancel subscriptions and adjust route.
{
...
created: function() {
const unsubscribe = this.$store.subscribe( ( mutation, state ) => {
if ( mutation.type === "name-of-your-mutation" && state.yourInfo === desiredValue ) {
unsubscribe();
this.$router.push( { name: "name-of-your-new-route" } );
}
} );
},
...
}

Related

Does returning a value in a Vuex action defeat the whole purpose of the Store?

Say I have an action like this:
async getYoutubeReport({ commit }, payload) {
return await this.$axios
.get(
`youtube/reports/${payload.date}/${payload.coin}`,
).then((res) => {
let today = utils.yearMonthDay(new Date())
let report = coinData.data.Items.find(Report => utils.yearMonthDay(new Date(Report.createdAt)) == today)
commit('SET_YOUTUBE_REPORT', report)
return report
})
}
}
Does this make any sense at all?
After all, the whole purpose of the Vuex Store is to horizontally make variables accesible across the app which can easily be done with reactive getters.
If the answer is "Yes, it is totally Fine", under which circumstances would make sense?
IMO, the only circumstance where I see it useful and where I do it is for showing a message response from the API in your Vue component.
If you need to access your state data you should use reactive getters.
Otherwise, there is no reason to maintain a vuex store and use data returned from your API.

Change state directly in action in vuex

I really don't get the disadvantage of setting of state in actions. Ok mutation is useful for vue-devtools but anything else? is there any code sample to show the handicap?
There is a better way to do this:
Actions allows to have asynchronous calls, this means that you can do https request, wait for and answer and commit (call a mutation).
Mutations are synchronous, because here is where the state is being updated.
So, if you doesn't require an asynchronous call, you can call the mutation right from the component:
// Using this.$store.commit()
// some component
...
methods: {
callAMutation() {
const someValue = "Update the vuex state with this";
// call the mutation without call an action
this.$store.commit("theMutationName", somevalue);
// if the store is using modules
// this.$store.commit("moduleName/theMutationName", somevalue);
}
}
...
Now using { mapMutations }
// some component
<script>
import { mapMutations } from 'vuex';
...
methods: {
...mapMutations(["theMutationName"]),
// again, if you have a store with modules, use the next format
// ...mapMutations({ aModuleMutation: "moduleName/theMutationName"})
callAMutation() {
const someValue = "Update the vuex state with this";
// call the mutation without call an action
// call the mutation ["theMutationName"] as a method
this.theMutationName(somevalue);
// if the store is using modules, call the mutation as a method
// this.aModuleMutation(somevalue);
}
}
...
</script>
This way you reduce the code write code, because the action is not required and it's useful for share code between components that use the store.
The reason to have mutations is because: One of the driving requirements of modern state management tools is traceability [https://blog.logrocket.com/vuex-showdown-mutations-vs-actions-f48f2f7df54b/], mutations allows to know where, how and when the state change, that way you can track which component is calling some action or mutation, debugging a big application could be painful.
But... In one of the vue mastery courses, I heard to Damian Dulisz said that mutation and actions will be merged, if so, you will set the state in the actions directly.

Returning axios promises from vuex action

I can't figure out, if it's a bad approach to have vuex actions returning axios promises like the example below. The reason why I do it, is because I need some values from the vuex state in the request. Can anyone tell me, if this is bad to do or totally fine? :)
/* Vuex action */
fetchSomething({ state }, id) {
return getSomething(
id,
state.basket.shipping,
state.basket.billing);
},
/* Method in vue component */
someMethod() {
this.$store.dispatch('fetchSomething')
.then(res => console.log(res));
}
I don't see the point of doing this. The point of a Vuex action is to do the asynchronous job of retrieving the data and then to mutate the Vuex store.
Once the store has been mutated, you can access it from your Vue component. As Vuex is part of the Vue ecosystem it follows the same reactivity principles as Vue. If you need to execute something once you have retrieved the data you can set a watcher on the variable you are retrieving or more commonly just use it as a computed property.

Calling method of another router-view's component / Vue.js

I have two <router-view/>s: main and sidebar. Each of them is supplied with a component (EditorMain.vue and EditorSidebar.vue).
EditorMain has a method exportData(). I want to call this method from EditorSidebar on button click.
What is a good way of tackling it?
I do use vuex, but i don't wanna keep this data reactive since the method requires too much computational power.
I could use global events bus, but it doesn't feel right to use it together with vuex (right?)
I could handle it in root of my app by adding event listener to router-view <router-view #exportClick="handleExportData"> and then target editor component, but it does not feel right as well as later i could need 100 listeners.
Is there any good practice for this? Or did i make some mistakes with the way app is set up? Did is overlooked something in documentation?
After two more years of my adventure with Vue I feel confident enough to answer my own question. It boils down to communication between router views. I've presented two possible solutions, I'll address them separately:
Events bus
Use global events bus (but it doesn't feel right to use it together with vuex)
Well, it may not feel right and it is surely not a first thing you have to think about, but it is perfectly fine use-case for event-bus. The advantage of this solution would be that the components are coupled only by the event name.
Router-view event listeners
I could handle it in root of my app by adding event listener to router-view <router-view #exportClick="handleExportData"> and then target editor component, but it does not feel right as well as later i could need 100 listeners.
This way of solving this problem is also fine, buy it couples components together. Coupling happens in the component containing <router-view/> where all the listeners are set.
Big number of listeners could be addressed by passing an object with event: handler mapping pairs to v-on directive; like so:
<router-view v-on="listeners"/>
...
data () {
return {
listeners: {
'event-one': () => console.log('Event one fired!'),
'event-two': () => console.log('The second event works as well!')
}
}
You could create a plugin for handling exports:
import Vue from 'vue'
ExportPlugin.install = function (Vue, options) {
const _data = new Map()
Object.defineProperty(Vue.prototype, '$exporter', {
value: {
setData: (svg) => {
_data.set('svg', svg)
},
exportData: () => {
const svg = _data.get('svg')
// do data export...
}
}
})
}
Vue.use(ExportPlugin)
Using like:
// EditorMain component
methods: {
setData (data) {
this.$exporter.setData(data)
}
}
// EditorSidebar
<button #click="$exporter.exportData">Export</button>

vue2: can not find a proper way to initialize component data by ajax

I have a component whose data is initialized by ajax. I know vue.js has provide several lifecycle hooks: Lifecycle-Diagram. But for ajax to initialize the data, which hook(beforeCreate, create, mounted, etc) is the best place to do it:
hook_name: function() {
ajaxCall(function(data) {
me.data = data;
});
}
Currently, i do it in mounted, making it to re-render the component. But i think we should get the data before the first render. Can someone figure out the best way to do it?
If you want to initialize your component with data you receive from a request, created() would be the most appropriate hook to use but it is a request, it might not resolve by the end of created or even mounted() (when even your DOM is ready to show content!).
So do have your component initialized with empty data like:
data () {
return {
listOfItems: [],
someKindOfConfig: {},
orSomeSpecialValue: null
}
}
and assign the actual values when you receive them in your created hook as these empty data properties would be available at that point of time, like:
created () {
someAPICall()
.then(data => {
this.listOfItems = data.listOfItems
})
/**
* Notice the use of arrow functions, without those [this] would
* not have the context of the component.
*/
}
It seems like you aren't using (or aren't planning to use) vuex but I'd highly recommend you to use it for for managing your data in stores. If you use vuex you can have actions which can make these api calls and by using simple getters in your component you would have access to the values returned by the request.