I am trying to add an section to a survey object and its throwing state mutation error.
this is the method where im calling the action creator which takes the entire survey object as an argument.
addNewSection(sectionName){
const id = performance.now();
let newSurvey = Object.assign({}, this.state.survey);
const section = Object.assign({},{
"type": "section",
"id": id,
"title": sectionName,
"position": {
"offset": 0,
"width": 12,
"order": 1
},
"items": []
});
newSurvey.layout.sections.push(section);
this.setState({survey: newSurvey});
this.props.actions.updateSurvey(newSurvey);
}
action Creator:
export function updateSurvey(survey){
return {type:types.ADD_SECTION_SUCCESS, survey};
}
reducer:
export default function surveyReducer(state = initialState.survey, action){
switch(action.type){
case types.ADD_SECTION_SUCCESS:
return action.survey;
default:
return state
}
}
the state object is of the form:
survey: {
layout: {
sections: [{},{},{}]
},
questions:[{},{},{}]
}
I must be misunderstanding Object.assign. Does Object.assign make a copy of every single nested object inside the survey, if i just use it at the very top level of the survey Object like i used it here?
First of all, the solutions mentioned above are completely wrong. Object.assign and ES6 spread operators never work on deeply nested data structures such as yours. They WON'T prevent any state mutations.
Secondly, state mutation errors are ALWAYS related to REDUX's state and NOT the local state.
export default function headerReducer(state = {userName: ''}, action) {
switch (action.type) {
case 'SSO_USER_ACTION':
{
return Object.assign({}, state, { userName: action.payload });
}
default:
return state;
}
}
Check out the above sample reducer. Here we always return a new object by using:
return Object.assign({}, state, { userName: action.payload });
However, in your case, the state object 'survey' is not that simple. It is deeply nested and the Object.assign OR the spread operator won't help at all to prevent a mutation. You have 3 options: