Jimmy
Jimmy

Reputation: 3880

Mutating redux states between dispatches

I am using React and Redux and trying to dispatch an action from a component but am getting the following error:

AJAX CALL Invariant Violation: A state mutation was detected between dispatches, in the path dashboard.categories.0.onClick. This may cause incorrect behavior. (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)

You can assume the following:

menu = [
  {title: "video", onClick: false},
  {title: "gif", onClick: false},
  {title: "website", onClick: false}
];

title = 'video';

Here is the component function being used:

itemCheck() {
  const {menu, title, actions} = this.props;
  actions.updateCategoryClick(title, [...menu]);
}

Redux Action:

export function updateCategoryClickSuccess(categories) {
  return {type: actionTypes.UPDATE_CATEGORY_CLICK_SUCCESS, categories};
}

export function updateCategoryClick(category, categories) {
  return function(dispatch) {
    dispatch(beginAjaxCall());
    return Utils.updateMenuClick(category, categories)
      .then(categories => {dispatch(updateCategoryClickSuccess(categories));})
      .catch(error => {dispatch(ajaxCallError(error));});
  };
}

And the utility function being used:

export function updateMenuClick(item, menu) {
  return new Promise((resolve) => {
    const index = menu.findIndex((object => object.title === item));
    menu[index].onClick = !menu[index].onClick;
    resolve(menu);
  });
}


Question: Why am I being told that a state mutation is happening between dispatches if in fact what I am sending is not the original state (in this case menu), but am instead sending it a new copy of the state (in this case [...menu])? And how do I fix this? Thanks all!

Upvotes: 0

Views: 359

Answers (1)

Roy Wang
Roy Wang

Reputation: 11270

[...menu] creates a shallow copy, so this.props.menu[index] and [...menu][index] are still referencing the same object.

Try replacing menu[index].onClick = !menu[index].onClick with:

menu[index] = { ...menu[index], onClick: !menu[index].onClick }

Might be better to do this logic in the reducer though.

Also, returning a promise in your updateMenuClick is a bit pointless since it's not async.

Upvotes: 3

Related Questions