How to fix The return value of the reducer must be a new object, not the draft in React
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.
Quick fix
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
});
});- Fix arrow functions with implicit returns
- Avoid mixing mutation and return patterns
- Check for Immer version conflicts
- Use current() utility for debugging
// 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
What this error means
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.
Typical symptoms
- Redux Toolkit throws error: "The return value of the reducer must be a new object, not the draft"
- Application crashes when dispatching certain actions
- Error appears in console when using arrow functions with implicit returns in reducers
- State updates fail to apply correctly
- Error occurs after updating to newer versions of Immer or Redux Toolkit
Common causes
- Using arrow functions with implicit returns that both mutate and return
- Attempting to return the draft state object instead of letting Immer handle it
- Mixing mutation syntax with return statements in the same reducer
- Version mismatch between Immer and Redux Toolkit
- Manually importing and using Immer's produce function inside Redux Toolkit reducers
How to fix "The return value of the reducer must be a new object, not the draft"
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.
Advanced notes
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.
Frequently asked questions
- What does "The return value of the reducer must be a new object, not the draft" mean?
- 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.
- How do I fix "The return value of the reducer must be a new object, not the draft"?
- 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.
- What causes "The return value of the reducer must be a new object, not the draft"?
- Using arrow functions with implicit returns that both mutate and return Attempting to return the draft state object instead of letting Immer handle it Mixing mutation syntax with return statements in the same reducer
- Is the "The return value of the reducer must be a new object, not the draft" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.