I am new to Vue and Vuex and I am still not crystal clear on when I should commit a state change during an async event?
Actions are where async operations and mutators should be called. However, within an action, should I call the mutator first before an async operation (let's say an api call to save the data), OR should I call the api first and within it's successful callback, call the mutator to commit the data changes to the state.
Thanks. Will also be very helpful if anyone can refer me to articles on this concept.
* More details on my question *
Given the following model where one budget can have multiple budget categories
state.budgets = {
1: {
month: 'October',
budgetCategories: {
1: {
id: 1,
name: 'Grocery',
budgeted: 350
},
2: {
id: 2,
name: 'Rent',
budgeted: 1000
},
3: {
id: 3,
name: 'Utilities',
budgeted: 150
}
},
2: {
month: 'November',
budgetCategories: {
1: {
id: 1,
name: 'Grocery',
budgeted: 350
},
2: {
id: 2,
name: 'Rent',
budgeted: 1000
},
3: {
id: 3,
name: 'Entertainment',
budgeted: 100
},
4: {
id: 4,
name: 'Tuition',
budgeted: 15000
}
}
},
...
}
}
Supposed we want to update a specific budget category for a month like the following: state.budgets[target.key].budgetCategories[target.categoryId].budgeted = 200
We have two ways to do this:
- Deep copy the model first before calling the api and mutator:
// mutation.js
UPDATE_BUDGET_CATEGORY(state, payload) {
state.budgets[payload.budget.id].budgetCategories[payload.budgetCategory.id] = payload.budgetCategory
}
// action.js
export const updateBudgetCategory = ({
commit,
dispatch,
getters
}, data) {
// deep copy the model
let budget = { ...getters.getBudgetById(data.budget.id)
}
const newBudget = data.budgetCategory.budgeted
const oldBudget = budget.budgetCategories[data.budgetCategory.id].budgeted
if (oldBudget !== newBudget) {
// update the model
budget.budgetCategories[data.budgetCategory.id].budgeted = newBudget
// api call to save the updated model
return api.updateBudget(budget).then(
response => {
// commit to store state
commit('UPDATE_BUDGET_CATEGORY', data)
},
error => {
// don't commit
console.log(error.message)
}
)
}
}
Pros: This is the right sequence. Data in the store state is consistent with that in the database.
Cons: Have to deep copy the model every time when updating the object. Besides, the code for updating the deep cloned model is fundamentally the same as that in the mutators. Definitely seems redundant and not DRY.
- Commit the data changes first then retrieve the model from the state to commit it to the database:
// action.js
export const updateBudgetCategory = ({
commit,
dispatch,
getters
}, data) {
// deep copy the model
const newBudget = data.budgetCategory.budgeted
const oldBudget = getters.getBudgetById(data.budget.id).budgetCategories[data.budgetCategory.id].budgeted
if (!oldBudget !== newBudget) {
commit('UPDATE_BUDGET_CATEGORY', data)
//api call to save the updated model from the state
return api.updateBudget(getters.getBudgetById(data.budget.id))
}
}
Pros: Code is DRY and clean. We tell the mutator what happened, and it takes care of updating the state.
Cons: So what to do when the api call fails? Is there a build-in revert-commit in Vuex?