2
votes

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:

  1. 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.

  1. 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?

1
If the mutation doesn't rely on the result of the async call, you don't need to use an action at all. Just commit the mutation and make the ajax call from your Vue instance.craig_h

1 Answers

1
votes

If the state you are updating is dependent on the async call result, then obviously you need to wait for the call to finish and the commit the result.

If it's not related to the async call result then you can commit it immediately.