How can my vuex mutation dispatch a new action? - vuejs2

How can my vuex mutation dispatch a new action or how can my action get read access to the store?
Basically I've got a action that calls an mutation:
updateSelectedItems: (context, payload) => {
context.commit('updateSelectedItems', payload);
},
And the mutation that updates the list. It also gets any new items. I need to do something with these new items:
updateSelectedItems: (state, payload) => {
var newItems = _.differenceWith(payload, state.selectedItems, function (a, b) {
return a.name === b.name;
});
state.selectedItems = _.cloneDeep(payload);
_.each(newItems, (item) => {
// How do I do this??
context.dispatch('getItemDetail', item.name)
});
},

It's really not best practice to make your mutations do too much. It's best if they're super-simple and generally do one thing. Let your actions take care of any multi-step processes that might affect the state.
Your example would make more sense structured like this:
actions: {
updateSelectedItems(context, payload) {
var selectedItems = context.state.selectedItems;
var newItems = _.differenceWith(payload, selectedItems, (a, b) => {
return a.name === b.name;
});
context.commit('setSelectedItems', payload);
_.each(newItems, (item) => {
context.dispatch('getItemDetail', item.name)
});
},
getItemDetail(context, payload) {
// ...
}
},
mutations: {
setSelectedItems(state, payload) {
state.selectedItems = _.cloneDeep(payload);
}
}
If you really need to dispatch something from inside a mutation (which I'd highly recommend not doing), you can pass the dispatch function to the mutation as part of the payload.

It is technically possible using this to call dispatch or commit (or few others) ... i'm only mentioning this for anybody who comes here and needs it for their specific use case.
In my situation i'm using fiery-vuex library which actually passes a function as the payload that will return the updated data, i use this along with a refresh_time key in the db to determine when to refresh certain user data
SOME_MUTATION( state, getData() ){
const new_data = getData()
if( new_data.refresh_time > state.user.refresh_time ){
this.dispatch( 'refreshFromOtherStateMeta', state.user.id )
}
state.user = new_data
}

Related

Vuex: Difference between using an Action vs. handling within component?

I am confused at why actions in Vuex can't just be handled within a component.
Assuming I have a basic store:
store.js
const initialState = () => ({
overlayText: ''
})
const mutations = {
setOverlayText: (state, payload) => {
state.overlayText = payload;
},
}
const actions = {
clearOverlay: (context, data) => {
return axios.get(data.url).then((response) => {
context.commit('setOverlayText', response);
});
},
}
If I want to make an API call and change data based off it like below using a Vuex Action:
Option 1
<button #click="dispatchClearOverlay">Get Data</button>
methods: {
clearOverlay() {
this.$store.dispatch('clearOverlay', {
url: '/api/clear-overlay',
})
}
}
what is the difference of just doing it within the component like this?
Option 2
<button #click="clearOverlay">Get Data</button>
methods: {
clearOverlay() {
axios.get('api/clear-overlay')
.then(resp => {
this.$store.commit('setOverlayText', response);
})
}
}
The examples you gave are slightly different in that in Option 1, the only possible value that will get stored in state.overlayText is the response from /api/clear-overlay. But in Option 2, you could pass any arbitrary text when you commit the mutation and that value would be stored in state.overlayText.
More generally, there are some important differences. Mutations have to be synchronous and Actions can be asynchronous. You can also fire multiple mutations by dispatching a single action (imagine if you frequently needed to call the same three mutations). These two features can help keep your components nice and lean, while centralizing more of the Store logic.
The Dispatching Actions section of the Actions docs helps illustrate these points.

Update a data in database using vuex

I'm struggling to implement an EDIT_DETAILS feature in vuex but I can implement this without using vuex but I prefer to use vuex because I am practicing my vuex skills.
Below snippets are the code that I am using to make my edit feature work.
this is in my profile.vue
editUser(id) {
this.id = id;
let details = {
id: this.id,
FULL_NAME: this.personDetails[0].FULL_NAME,
EMAIL: this.personDetails[0].EMAIL
};
//this will pass the details to my actions in vuex
this.editDetails(details);
}
personDetails, just retrieves the details of my user in my database.
id is the user number which is the primary key of my table in my backend.
below is the example json came from my database
this is my action in my vuex:
async editDetails({ commit }, payload) {
try {
const response = await axios.put("http:/localhost:9001/profile/edit/" + payload);
commit("EDIT_DETAILS", response.data);
} catch (err) {
console.log(err);
}
}
and this is my mutation:
EDIT_DETAILS(state, detail) {
state.details.findIndex((param) => param.id === detail);
let details = state.details
details.splice(details.indexOf(detail), 1)
state.details = details.body
}
and my state:
details: [],
Use a comma instead of plus in your axios request
Not sure what your response is but this does nothing
state.details.findIndex((param) => param.id === detail);
You need to push into array if not exists

Can I dispatch vuex action inside getter function?

I want to dispatch action inside getter function.
1. Is it possible and right.
2. If yes how can I do it?
I guess it will be something like this dispatch('GET_BOOKS');
const getters = {
getAllBooksDispatch: (state, getters, dispatch) => {
if (state.books === null) {
dispatch('GET_BOOKS');
}
return state.books
},
};
But it does not work.
So my store file looks like this.
const initialState = {
books: null
};
const getters = {
getAllBooksDispatch: (state, getters, dispatch) => {
if (state.books === null) {
dispatch('GET_BOOKS');
}
return state.books
},
};
const mutations = {
SET_BOOKS: (state,{data}) => {
console.log('SET_BOOKS mutations')
state.books = data;
},
};
const actions = {
GET_BOOKS: async ({ commit }) => {
let token = users.getters.getToken;
let query = new Promise((resolve, reject) => {
axios.get(config.api + 'books', {token}).then(({data}) => {
if (data) {
commit('SET_BOOKS', {data: data})
resolve()
} else {
reject(data.message);
}
}).catch(() => {
reject('Error sending request to server!');
})
})
},
};
No, you can't. At least not the way you want to. The third argument in a getter is the rootState object when using modules, not dispatch. Even if you find a way to dispatch an action inside a getter it won't work the way you expect. Getters must be synchronous, but actions can be (and in this example are) asynchronous. In your example, GET_BOOKS would be dispatched but the getter would still return state.books as null.
I'd recommend handling this sort of lazy-loading outside of the Vuex store.

Returning data from a Vuex action

Can I return data from a Vuex action or do I need to update the store?
I've got an action defined but it returns no data:
getData() {
return { "a" : 1, "b" : 2 }
}
You can actually return data from an action. From the documentation:
Actions are often asynchronous, so how do we know when an action is
done? And more importantly, how can we compose multiple actions
together to handle more complex async flows?
You should return a promise and the data in the resolve() method:
actions: {
actionA () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: 'John Doe' })
}, 1000)
})
}
}
And use it this way:
store.dispatch('actionA').then(payload => {
console.log(payload) /* => { name: 'John Doe' } */
})
Your code will work if the code that calls your action uses either 'await' or a callback.
For example:
const result = await store.dispatch('getData');
The main takeaway being that actions return promises even if the action isn't doing any asynchronous work!

Structuring a Vuex module

I am having some trouble in trying to keep my Vuex modules clean and I was hoping to receive some insight on how to improve this. I have already split up some mutations and am using actions to compose multiple mutations so I guess that is a good start.
In most examples I see super clean mutations and I have those as well but a lot I needs checks with if statements or other side effects. To provide examples:
My action:
setFilteredData({ state, commit }, payload) {
commit('setFilteredData', payload);
// Check if we need to split up the data into 'hotels' and 'nearby_hotels'.
if (state.filteredData.find(hotel => hotel.nearby_city)) {
commit('splitHotelsAndNearbyHotels', state.filteredData);
}
}
My mutation:
splitHotelsAndNearbyHotels(state, payload) {
// Chunk it up into hotels and nearby hotels.
const composed = groupBy(payload, 'nearby_city');
if (composed.true) {
composed.true.forEach((hotel) => {
if (hotel.isFirst) hotel.isFirst = false;
});
composed.true[0].isFirst = true;
// Merge them back together in the right order.
state.filteredData = composed.false.concat(composed.true);
}
}
In this example if my array of objects contains a hotel with hotel.nearby_city set to true it will perform the commit of splitHotelsAndNearbyHotels.
The code is not transparent enough. The if statement inside the action does not feel right and I would like my mutation to be cleaner.
I have thought about splitting up my splitHotelsAndNearbyHotels into separate functions but I have no idea where to place those. Simply putting them inside the Vuex file does not feel like a big improvement putting them in a separate file could be an option I guess.
How could I clean up my file to improve the readability? Perhaps someone can show me a Vuex example which does not have an ideal scenario like what I am dealing with.
Actually you can move your actions code into getters, it's more clean to use single source and filter it on getter.
But if you insist using action you can move your mutation code inside on action, and restructure your actions code just like this:
Helper.js
This is for provide data and helper functions:
var _ = require('lodash');
const payloadData = [
{"name":"A", "nearby_city":true, "isFirst":true},
{"name":"B", "nearby_city":false, "isFirst":false},
{"name":"C", "nearby_city":false, "isFirst":false},
{"name":"D", "nearby_city":true, "isFirst":false}
];
// assumed nearby_city is boolean
const isNearby = (hotels) => { return !!hotels.find(hotel => hotel.nearby_city === true) };
const groupBy = (items, key) => { return _.groupBy(items, item => item[key]) };
Mutations.js
This is your mutation looks now:
const mutations = {
setfilteredData : (state, hotels) => {
state.filteredHotels = hotels || [];
},
}
Actions.js
And this is your actions, it's fine without moving your functions into separate files.
// separate filter function
const filterNearby = (payload) => {
if(isNearby(payload) === false){
return payload;
}
const composed = groupBy(payload, 'nearby_city');
composed.true.forEach((hotel) => {
if (hotel.isFirst) hotel.isFirst = false;
});
composed.true[0].isFirst = true;
return composed.false.concat(composed.true);
};
const actions = {
setfilteredData: ({state, commit}, payload) => {
/**
* Using separate filter function
*/
commit('setfilteredData', filterNearby(payload));
return;
/**
* Using restructured code
*/
// Check if we need to split up the data into 'hotels' and 'nearby_hotels'.
if(isNearby(payload) === false){
commit('setfilteredData', payload);
return;
}
// Chunk it up into hotels and nearby hotels.
const composed = groupBy(payload, 'nearby_city');
composed.true.forEach((hotel) => {
if (hotel.isFirst) hotel.isFirst = false;
});
composed.true[0].isFirst = true;
// Merge them back together in the right order.
commit('setfilteredData', composed.false.concat(composed.true));
}
};