Josh L
Josh L

Reputation: 1492

Can't figure out how to change State in reducer without causing a mutation

I keep getting the following error thrown after trying to update state in my reducer:

"Error: A state mutation was detected inside a dispatch, in the path: quiz.questions.0.answer. Take a look at the reducer(s) handling the ac..."

I'm pretty sure I am not mutating the original state as I am using object.assign yet the error persists.

My reducer:

    case types.UPDATE_QUIZ_ANSWER:
        let newStateObject =  Object.assign({}, state);
        let currentQuestion = newStateObject.questions.find(x => x.id == parseInt(action.data.questionId));
        currentQuestion.answer = action.data.answer;
        return Object.assign({}, newStateObject); 

My state object:

{"questions":
    [{"id":1,"questionText":"Camps is?","multipleChoiceOption1":
     {"value":"1","label":"Great"},"multipleChoiceOption2":
     {"value":"2","label":"Fun"},"multipleChoiceOption3":
     {"value":"3","label":"Awesome"},"multipleChoiceOption4":
     {"value":"4","label":"All of the above"},
     "answer":"2"},
     {"id":2,"questionText":"At Camps we will?","multipleChoiceOption1":
     {"value":"1","label":"AAA"},"multipleChoiceOption2":
     {"value":"2","label":"Adult Focused"},"multipleChoiceOption3":
     {"value":"3","label":"CCC"},"multipleChoiceOption4":
     {"value":"4","label":"All of the above"},
      "answer":"3"}],
  "results":
     {"quizPass":false,"quizResults":[]}}"

Upvotes: 0

Views: 897

Answers (2)

Artur A
Artur A

Reputation: 9119

Spread operator ... aka Object.assign works only on the first level of object properties.

let newStateObject =  Object.assign({}, state);
let currentQuestion = newStateObject.questions.find(x => x.id == parseInt(action.data.questionId));
currentQuestion.answer = <----- here is the mutation

What you can do is to create a copy of object at each level:

// make a copy of the array
let newQuestions = [...newStateObject.questions];
let questionIndex = newQuestions.findIndex(x => x.id == parseInt(action.data.questionId));
// update array and question
newQuestions[questionIndex] = {...newQuestions[questionIndex], answer: action.data.answer};
// return new result
return  {...state, questions: newQuestions};

Upvotes: 2

Richard Szalay
Richard Szalay

Reputation: 84724

This code is mutating your state:

let currentQuestion = newStateObject.questions.find(x => x.id ==
    parseInt(action.data.questionId));
currentQuestion.answer = action.data.answer;

Object.assign does not deep clone so, while your newStateObject is certainly not the same as your original state, the currentQuestion you are pulling from the array of questions is the same as in the original state object graph.

You may want to look into something like Immutable.js, but think about it like this: the actual object you want to change needs to be replaced, as does its parent (the object/array that references it) and so forth until the top of your state. If you are using combineReducers you only have to worry about the top of that slice since combineReducers does the work above that.

Upvotes: 1

Related Questions