Returning axios promises from vuex action - vue.js

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.

Related

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.

Will vuex work properly with generic mutations that don't reference `state`?

I’m using vuex with a tree of data. It's reactive and works well because we have a corresponding tree of components. Since the structure is a tree, it’s common to want to mutate a deeply nested child object. The easy way to implement that is with a mutation that accepts the child in its payload:
removeLeaf(state, { child, leaf }) {
child.children = child.children.filter((i: any) => i !== leaf);
state = state; // silence warning
},
A different way would be to change the mutation to work like this:
removeLeaf(state, { child_, leaf }) {
let child = searchForChild(state, child_);
child.children = child.children.filter((i: any) => i !== leaf);
},
I’m happy with the first way. Are there any drawbacks to writing mutations that modify a child object of state by using payload instead of the state parameter?
I don't think it'll work properly.
Vue's and Vuex's reactivity system is based on Javascript Getter and Setter.
If you console.log the state in any mutation, you will see something like this:
The get and set is the getter and setter of your Vuex States.
Without the setter, Vue's reactivity system likely wouldn't work.
Try console.log state and child in your mutation. You will likely see that the child doesn't contain setter.
If the setter is not there, Vue wouldn't know that you have updated the state of child, and you will likely have reactivity problem.

Handsontable edit triggers Vuex mutation errors

I am currently working on a big data grid using Handsontable and Vue and my data is stored in Vuex. Problem is, when I edit a cell I get Vuex mutation errors. In ag-grid I can use valueSetters and getters to avoid that, but I can't find how to do that in Handsontable. Also, afterChange events are not fired because of mutation errors. Computed value get and set also do not help me. Anyone had the same issue? I can probably write custom editor, but it is last thing I want to do, so may be there is another solution.
Thank you.
Option 1: Make sure that you supply a copy of the data from the vuex store to Handsontable's settings.data property. This way, vuex will not complain when Handsontable changes the data, but you will have to make sure that all changes gets committed to the store.
Example:
get settings() {
return {
data: JSON.parse(JSON.stringify(this.data)),
};
}
Option 2: Add a beforeChange hook that commits the change to the store and then returns false. This makes all changes ignored by Handsontable. This will make sure that Handsontable always displays what is committed to the vuex store. Caveat: This also means that Handsontable will show the old value directly after the cell has been edited until it has been committed to the store.
Example:
public beforeChange(changes, source) {
if (source === "edit") {
changes
.map((change, i) => {
const [index, attribute, oldValue, newValue] = change;
const oldRow = this.settings.data[index];
this.$store.dispatch("rowChange", { data: oldRow, index, attribute, oldValue, newValue });
});
// disregard all changes until they are propagated via vuex state commits
return false;
}
}
(If you share some specific code examples, it will be easier to help in detail)
Do not mutate data directly, it's an anti pattern and most probably you will get errors if caught doing that.
Try writing mutations in Vuex and commit those mutations with payload data that you want to update. Write all the state updation logic inside mutations.
If data is fetched from an async source try dispatching actions for every change, and in those actions commit mutations and rest is same as above.
I hope it helps.

How to access Vuex modules mutations

I read thorugh his documentation from vue but I didn't find anything about how to actually access specific module in the store when you have multiple modules.
Here is my store:
export default new Vuex.Store({
modules: {
listingModule: listingModule,
openListingsOnDashModule: listingsOnDashModule,
closedListingsOnDashModule: listingsOnDashModule
}
})
Each module has its own state, mutations and getters.
state can be successfully accessed via
this.$store.state.listingModule // <-- access listingModule
The same is not true for accessing mutations cause when I do this
this.$store.listingModule.commit('REPLACE_LISTINGS', res)
or
this.$store.mutations.listingModule.commit('REPLACE_LISTINGS', res)
I get either this.$store.listingModule or this.$store.mutations undefined error.
Do you know how should the module getters and mutations be accessed?
EDIT
As Jacob brought out, the mutations can be accessed by its unique identifier. So be it and I renamed the mutation and now have access.
here is my mutation:
mutations: {
REPLACE_OPEN_DASH_LISTINGS(state, payload){
state.listings = payload
},
}
Here is my state
state: {
listings:[{
id: 0,
location: {},
...
}]
}
As I do a commit with a payload of an array the state only saves ONE element.
Giving in payload array of 4 it returns me back array of 1.
What am I missing?
Thanks!
It's a good idea, IMHO, to call vuex actions instead of invoking mutations. An action can be easily accessed without worrying about which module you are using, and is helpful especially when you have any asynchronous action taking place.
https://vuex.vuejs.org/en/actions.html
That said, as Jacob pointed out already, mutation names are unique, which is why many vuex templates/examples have a separate file called mutation-types.js that helps organize all mutations.
re. the edit, It's not very clear what the issue is, and I would encourage you to split it into a separate question, and include more of the code, or update the question title.
While I can't tell why it's not working, I would suggest you try using this, as it can resolve two common issues
import Vue from 'vue'
//...
mutations: {
REPLACE_OPEN_DASH_LISTINGS(state, payload){
Vue.$set(state, 'listings', [...payload]);
},
}
reactivity not triggered. Using Vue.$set() forces reactivity to kick in for some of the variables that wouldn't trigger otherwise. This is important for nested data (like object of an object), because vue does not create a setter/getter for every data point inside an object or array, just the top level.
rest destructuring. Arrays: [...myArray] Objects: {...myObj}. This prevents data from being changed by another process, by assigning the contents of the array/object as a new array/object. Note though that this is only one level deep, so deeply nested data will still see that issue.

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

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" } );
}
} );
},
...
}