This error occurs in Redux Toolkit when using Immer incorrectly in reducers. It happens when you try to both mutate the draft state AND return a new value, which violates Immer's rules. The fix involves understanding how Immer works with Redux Toolkit's createReducer or createSlice.
Fixes The return value of the reducer must be a new object, not the draft
// Incorrect - both mutates AND returns
const reducer = createReducer(initialState, (builder) => {
builder.addCase(someAction, (state, action) => {
state.value = action.payload; // Mutation
return state; // Explicit return - ERROR!
});
});
// Correct - only mutate
const reducer = createReducer(initialState, (builder) => {
builder.addCase(someAction, (state, action) => {
state.value = action.payload; // Just mutate, no return
});
});
// Also correct - only return (no mutation)
const reducer = createReducer(initialState, (builder) => {
builder.addCase(someAction, (state, action) => {
return { ...state, value: action.payload }; // Return new object
});
});// Incorrect - both mutates AND returnsconst reducer = createReducer(initialState, (builder) => {builder.addCase(someAction, (state, action) => {state.value = action.payload; // Mutationreturn state; // Explicit return - ERROR!});});// Correct - only mutateconst reducer = createReducer(initialState, (builder) => {builder.addCase(someAction, (state, action) => {state.value = action.payload; // Just mutate, no return});});// Also correct - only return (no mutation)const reducer = createReducer(initialState, (builder) => {builder.addCase(someAction, (state, action) => {return { ...state, value: action.payload }; // Return new object});});The return value of the reducer must be a new object, not the draft
The "return value must be a new object, not the draft" error is thrown by Immer when used with Redux Toolkit. Immer allows you to write reducers that appear to mutate state directly, but internally it tracks changes to produce an immutable update. This error occurs when you violate Immer's core rule: you must either mutate the draft state OR return a new state value, but not both. Redux Toolkit uses Immer internally with createReducer and createSlice, so this error typically appears when writing reducer logic incorrectly.
Change arrow functions that implicitly return to either mutate OR return explicitly:
// Incorrect - both mutates AND returns
const reducer = createReducer(initialState, (builder) => {
builder.addCase(someAction, (state, action) => {
state.value = action.payload; // Mutation
return state; // Explicit return - ERROR!
});
});
// Correct - only mutate
const reducer = createReducer(initialState, (builder) => {
builder.addCase(someAction, (state, action) => {
state.value = action.payload; // Just mutate, no return
});
});
// Also correct - only return (no mutation)
const reducer = createReducer(initialState, (builder) => {
builder.addCase(someAction, (state, action) => {
return { ...state, value: action.payload }; // Return new object
});
});Choose one pattern and stick with it throughout your reducer:
// Choose EITHER mutation style:
case 'UPDATE_VALUE':
draft.value = action.payload;
// No return statement - Immer handles it
break;
// OR return style:
case 'UPDATE_VALUE':
return {
...state,
value: action.payload
};
// NOT both:
case 'UPDATE_VALUE':
draft.value = action.payload; // Mutation
return draft; // Return - ERROR!Ensure Redux Toolkit and Immer versions are compatible:
# Check installed versions
npm list immer redux @reduxjs/toolkit
# If there's a version mismatch, let Redux Toolkit manage Immer:
npm uninstall immer
npm install @reduxjs/toolkit
# Redux Toolkit will bring its own compatible Immer versionRedux Toolkit ships with a specific Immer version. Manually installing a different Immer version can cause this error.
When you need to inspect draft state during debugging, use Immer's current() utility:
import { current } from '@reduxjs/toolkit';
const reducer = createReducer(initialState, (builder) => {
builder.addCase(someAction, (state, action) => {
console.log('Current draft state:', current(state));
state.value = action.payload;
// No return needed
});
});
// current() creates a plain copy for inspection without affecting the draftIf you need to convert existing code, here's how to switch patterns:
// From mutation to return style:
// Before:
state.items.push(action.payload);
// After:
return {
...state,
items: [...state.items, action.payload]
};
// From return to mutation style:
// Before:
return { ...state, count: state.count + 1 };
// After:
state.count += 1;Remember: With mutation style, you modify properties directly on the draft. With return style, you create and return a new object.
Redux Toolkit's createReducer and createSlice use Immer internally to enable "mutative" syntax while maintaining immutability. Immer works by creating a draft proxy that records mutations. When the reducer finishes, Immer produces the next immutable state based on those mutations. The error occurs when Immer detects both mutations AND a return value, which creates ambiguity about which state should be used. This is a protective measure to prevent subtle bugs. In complex reducers, you might need to use Immer's produce function directly for nested updates, but be careful not to mix patterns.