This error occurs when you try to directly mutate the state in a Redux reducer without using Immer. Redux requires immutable updates, and mutating state directly can cause components not to re-render and break time-travel debugging. This guide explains how to properly update state immutably in Redux reducers.
The error "Cannot mutate state in a non-immer reducer" indicates that you are attempting to modify the Redux state directly (e.g., by changing a property of an object or pushing to an array) in a reducer that is not wrapped by Immer. Redux relies on immutable state updates to detect changes efficiently. When you mutate state directly, Redux's shallow equality checks fail to detect the change, which can lead to UI not updating, broken time-travel debugging, and unpredictable behavior.
Look for places in your reducer where you are directly modifying the state object. Common patterns include assignment to properties (state.someField = action.payload), array methods that mutate in-place (push, pop, splice, sort), and modifying nested objects without copying.
For objects, use the spread operator to create a new object with the updated property:
return {
...state,
field: action.payload
};For arrays, use methods that return new arrays:
// Adding an item
return [...state, action.payload];
// Removing an item
return state.filter(item => item.id !== action.payload);
// Updating an item
return state.map(item =>
item.id === action.payload.id ? { ...item, ...action.payload } : item
);Redux Toolkit includes Immer by default in createSlice and createReducer. You can write code that looks like it mutates state, but Immer safely produces immutable updates:
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
todoAdded(state, action) {
// This looks like a mutation but is safe with Immer
state.push(action.payload);
}
}
});Make sure you are using Redux Toolkit version 1.0 or later.
Ensure your reducer does not have side effects (like API calls, DOM manipulation) and always returns a new state object when changes occur. If no changes are needed, return the existing state object unchanged.
After making changes, verify that your UI updates correctly and that Redux DevTools shows proper state changes. Use time-travel debugging to ensure each action produces a clean state snapshot.
When using Immer with Redux Toolkit, remember that the "mutating" syntax only works inside reducers created with createSlice or createReducer. If you write a reducer manually without Immer, you must use traditional immutable update patterns. Also, Immer does not work with primitive state values (strings, numbers, booleans) – for those, you must return a new value directly. For large nested state updates, Immer can be more performant than manual immutable updates because it uses structural sharing.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite