admin 管理员组

文章数量: 1086019

I know this question has been asked before, but in almost all of them talk about the OP mutating the state directly, which I've tried to avoid using techniques like the spread operator (in both objects and arrays) but in spite of that I'm getting the following error:

Uncaught Error: A state mutation was detected between dispatches, in the path `courses.0`. This may cause incorrect behavior. (.html#never-mutate-reducer-arguments)
  at invariant (eval at <anonymous> (bundle.js:922), <anonymous>:40:15)
  at eval (eval at <anonymous> (bundle.js:3825), <anonymous>:50:36)
  at eval (eval at <anonymous> (bundle.js:3846), <anonymous>:14:16)
  at dispatch (eval at <anonymous> (bundle.js:3853), <anonymous>:37:18)
  at eval (eval at <anonymous> (bundle.js:1151), <anonymous>:63:5)
  at eval (eval at <anonymous> (bundle.js:3846), <anonymous>:11:18)
  at Object.eval [as saveCourse] (eval at <anonymous> (bundle.js:3860), <anonymous>:4:12)
  at Object.ManageCoursePage._this.saveCourse [as onSave] (eval at <anonymous> (bundle.js:2080), <anonymous>:134:27)
  at CourseForm._this.onHandleSave (eval at <anonymous> (bundle.js:2052), <anonymous>:93:19)
  at Object.ReactErrorUtils.invokeGuardedCallback (eval at <anonymous> (bundle.js:1301), <anonymous>:69:16)

I read the article that the error points to, and as it suggests I checked if I was manually mutating the state in my reducer and I can't see where that could be happening:

const courseReducer = (state = initialState.courses, action) => {
  switch (action.type) {
    case types.LOAD_COURSE_SUCCESS:
      return action.courses;
    case types.CREATE_COURSE_SUCCESS:
    //This action throw the error 
      debugger;      
      return [
        ...state,
        Object.assign({}, action.course)
      ];
    case types.UPDATE_COURSE_SUCCESS:
     //This action throw the error
      debugger;
      return [
        ...state.filter(course => course.id !== action.course.id),
        Object.assign({}, action.course)
      ];
    case types.DELETE_COURSE_SUCCESS:
      return [...state.filter(course => course.id !== action.courseId)];
    default:
      return state;
  }
};

Here's a sandbox with part of the application that can be used to replicate the error, which weirdly only happens from times to times when I create a new course or try to edit an existing one (actions CREATE_COURSE_SUCCESS and UPDATE_COURSE_SUCCESSrespectively in my courseReducer)

I'd really appreciate if anyone could help me figure out where could be lying the source of this error.

I know this question has been asked before, but in almost all of them talk about the OP mutating the state directly, which I've tried to avoid using techniques like the spread operator (in both objects and arrays) but in spite of that I'm getting the following error:

Uncaught Error: A state mutation was detected between dispatches, in the path `courses.0`. This may cause incorrect behavior. (http://redux.js/docs/Troubleshooting.html#never-mutate-reducer-arguments)
  at invariant (eval at <anonymous> (bundle.js:922), <anonymous>:40:15)
  at eval (eval at <anonymous> (bundle.js:3825), <anonymous>:50:36)
  at eval (eval at <anonymous> (bundle.js:3846), <anonymous>:14:16)
  at dispatch (eval at <anonymous> (bundle.js:3853), <anonymous>:37:18)
  at eval (eval at <anonymous> (bundle.js:1151), <anonymous>:63:5)
  at eval (eval at <anonymous> (bundle.js:3846), <anonymous>:11:18)
  at Object.eval [as saveCourse] (eval at <anonymous> (bundle.js:3860), <anonymous>:4:12)
  at Object.ManageCoursePage._this.saveCourse [as onSave] (eval at <anonymous> (bundle.js:2080), <anonymous>:134:27)
  at CourseForm._this.onHandleSave (eval at <anonymous> (bundle.js:2052), <anonymous>:93:19)
  at Object.ReactErrorUtils.invokeGuardedCallback (eval at <anonymous> (bundle.js:1301), <anonymous>:69:16)

I read the article that the error points to, and as it suggests I checked if I was manually mutating the state in my reducer and I can't see where that could be happening:

const courseReducer = (state = initialState.courses, action) => {
  switch (action.type) {
    case types.LOAD_COURSE_SUCCESS:
      return action.courses;
    case types.CREATE_COURSE_SUCCESS:
    //This action throw the error 
      debugger;      
      return [
        ...state,
        Object.assign({}, action.course)
      ];
    case types.UPDATE_COURSE_SUCCESS:
     //This action throw the error
      debugger;
      return [
        ...state.filter(course => course.id !== action.course.id),
        Object.assign({}, action.course)
      ];
    case types.DELETE_COURSE_SUCCESS:
      return [...state.filter(course => course.id !== action.courseId)];
    default:
      return state;
  }
};

Here's a sandbox with part of the application that can be used to replicate the error, which weirdly only happens from times to times when I create a new course or try to edit an existing one (actions CREATE_COURSE_SUCCESS and UPDATE_COURSE_SUCCESSrespectively in my courseReducer)

I'd really appreciate if anyone could help me figure out where could be lying the source of this error.

Share Improve this question edited Dec 17, 2017 at 5:23 Andrew Li 58k14 gold badges134 silver badges148 bronze badges asked Jun 3, 2017 at 17:58 eddyeddy 4,41317 gold badges67 silver badges99 bronze badges 2
  • Can we see how you use your state, via mapStateToProps? – Andrew Li Commented Jun 3, 2017 at 18:06
  • Sure! It is a bit messy, but I already added under an Edit. Btw you can found the same code in the sandbox I included in my question, just look for the file ponents/course/ManageCoursePage.js – eddy Commented Jun 3, 2017 at 18:13
Add a ment  | 

1 Answer 1

Reset to default 7

The problem is that you are mutating state, inside CoursesPage.js, in mapStateToProps:

let courses = state.courses.sort((c1, c2) =>
  c1.title.localeCompare(c2.title, 'en', { sensitivity: 'base' }),
);

Array#sort is an in place operation, so it mutates the source array by sorting the source array in place. That means, sort is mutating state.courses, thus causing the error. Instead, make a copy of state.courses before you sort it:

[...state.courses]

The above uses spread syntax to spread the elements into a new array essentially cloning it. This is the same as Array#slice. Then you can sort it like so:

let courses = [...state.courses].sort((c1, c2) =>
  c1.title.localeCompare(c2.title, 'en', { sensitivity: 'base' }),
);

This will not mutate the original state.courses and will not throw an error.


Note: There are also some places where you are mutating this.state directly, such as in line 32 of ManageCoursePage.js where you do:

this.state.errors = {}

Only ever mutate the this.state object in the constructor of the ponent. Use setState instead.

本文标签: javascriptA state mutation was detected between dispatches but I haven39t mutated stateStack Overflow