Why Immutable Data Matters in React and How Immer Simplifies State Updates
This article explains how React relies on shallow comparison of immutable data to trigger re‑renders, demonstrates common pitfalls when mutating state directly, and shows how using Immer provides a concise, copy‑on‑write solution that reduces bugs, memory usage, and CPU overhead.
In React, a component re‑renders only when its state reference changes; therefore, immutable data (shallow comparison) is required for efficient updates.
Directly mutating an object’s property (e.g., changing
animal.namefrom "cat" to "dog") does not change the object’s memory address, so
setAnimaldoes not trigger a re‑render.
To update state correctly, a new object must be created by copying the original and then modifying the desired fields. This works for top‑level properties but becomes verbose for nested structures.
<code>const [animal, setAnimal] = useState({
name: 'cat',
body: { color: 'white' }
});
useEffect(() => {
animal.name = 'dog'; // no re‑render
setAnimal(animal);
}, []);
</code>When updating a nested property like
animal.body.color, the same issue occurs because the inner object’s address does not change.
<code>useEffect(() => {
animal.body.color = 'black'; // not detected
setAnimal(animal);
}, []);
</code>The proper immutable update requires recreating each level of the object hierarchy:
<code>setAnimal({
...animal,
name: 'dog',
body: { ...animal.body, color: 'black' }
});
</code>While this works, it leads to repetitive boilerplate and increased memory usage.
Using Immer
Immer documentation: https://immerjs.github.io/immer/zh-CN/
Immer creates a proxy (
draftState) of the current state. Mutations are applied to the draft, and Immer produces a new immutable state automatically.
<code>import { produce } from 'immer';
const [state, setState] = useState({ count: 1 });
useEffect(() => {
const nextState = produce(state, draft => {
draft.count = 2;
});
setState(nextState);
}, []);
</code>With the
useImmerhook, the code becomes even cleaner:
<code>const [state, setState] = useImmer({ count: 1 });
useEffect(() => {
setState(draft => {
draft.count = 2;
});
}, []);
</code>Applying Immer to the earlier animal example removes the need for manual copying:
<code>const [animal, setAnimal] = useImmer({
name: 'cat',
body: { color: 'white' }
});
useEffect(() => {
setAnimal(draft => {
draft.name = 'dog';
draft.body.color = 'black';
});
}, []);
</code>Immer’s copy‑on‑write mechanism only clones the parts of the state that are actually changed, keeping unchanged parts shared and reducing memory consumption.
Immer Features
Copy on Write (only modified parts are cloned)
<code>const [data, setData] = useState({ d1: { a: 1 }, d2: { a: 2 }, d3: { a: 3 } });
const next = produce(data, draft => {
draft.d1.a = 4;
});
// d2 and d3 remain the same objects
</code>Performance tests show that
nextState === datawhen no changes are made, and only the changed branches differ when mutations occur.
Memory Comparison
When updating a large object (e.g., a map of 1 MiB entries) with deep copies, each update creates a full copy of the data, leading to high memory usage and CPU overhead. Using Immer, only the modified path is copied, dramatically reducing the memory footprint.
<code>// Deep copy example (high memory)
setState(prev => ({ ...prev, data: { ...prev.data, key: newValue } }));
// Immer example (low memory)
setState(draft => { draft.data.key = newValue; });
</code>In summary, Immer simplifies immutable updates, reduces boilerplate, lowers the risk of bugs, and can improve performance by avoiding unnecessary deep copies.
Although the examples are simplified, they illustrate how Immer can lessen developers' mental load, reduce errors, and in some scenarios lower memory and CPU consumption without adverse side effects.
References
Discussion on Immer in the Redux Toolkit community: https://github.com/reduxjs/redux-toolkit/issues/242
Immer performance benchmarks: https://immerjs.github.io/immer/zh-CN/performance
Kuaishou E-commerce Frontend Team
Kuaishou E-commerce Frontend Team, welcome to join us
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.