I'm trying to build a React/Redux app and I am a beginner. I want to use the React Sortable HOC to have a rearrangeable list but I cannot get the new arrangement to stick. I have a functional component where I get the list of items. The item structure is like this: items [ {name, courseList}, {name, courseList}, ...].
To populate the table I make an api call and update the prop variable using MapStateToProps. Here's a bit of code:
function CoursesPage({
student,
studentCourses,
loadStudentCourses,
updateCoursesOrder,
...props
}) {
useEffect(() => {
if (studentCourses.length === 0) {
loadStudentCourses(student.id).catch((error) => {
alert("Loading student courses failed" + error);
});
}
}, []);
...
}
and this is the mapStateToProps function:
function mapStateToProps(state) {
return {
student: state.student,
studentCourses: state.studentCourses,
};
}
This bit works fine, and everything appears. The problem is when I try to rearrange and save it in onSortEnd function:
function onSortEnd({ oldIndex, newIndex, collection }) {
const newCollections = [...studentCourses];
newCollections[collection].courseList = arrayMove(
newCollections[collection].courseList,
oldIndex,
newIndex
);
updateCoursesOrder(newCollections);
}
The newCollection gets populated and modified correctly and I am calling updateCoursesOrder with the items correctly arranged. That function is an action that calls a dispatch.
export function updateCoursesOrder(courseList) {
return function (dispatch) {
dispatch(setNewCourseOrderSuccess(courseList));
};
}
export function setNewCourseOrderSuccess(studentCourses) {
return { type: types.SET_NEW_COURSE_ORDER, studentCourses };
}
Using the debugger I can see that the code is running well up till the dispatch return from setNewCourseOrderSuccess().
This should go to the reducer, but instead throws an error: Uncaught Invariant Violation: A state mutation was detected between dispatches.
This is how the reducer looks like:
export default function courseReducer(
state = initialState.studentCourses,
action
) {
switch (action.type) {
case type.LOAD_STUDENT_COURSES_SUCCESS:
return action.studentCourses;
case type.SET_NEW_COURSE_ORDER:
return {
...state,
studentCourses: action.payload,
};
default:
return state;
}
}
How could I solve this issue? Thank you very much!
With this:
const newCollections = [...studentCourses];
newCollections[collection].courseList =
Although newCollections
is a new array, it's only a shallow copy of the studentCourses
; the array items are not clones, they're references to current objects in state. So, assigning to a courseList
property of one of those objects is mutating the state.
Replace the object at the index instead of mutating it:
function onSortEnd({ oldIndex, newIndex, collection }) {
updateCoursesOrder(studentCourses.map((item, i) => i !== collection ? item : ({
...item,
courseList: arrayMove(item.courseList, oldIndex, newIndex)
})));
}