Frontend Development 8 min read

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.

Kuaishou E-commerce Frontend Team
Kuaishou E-commerce Frontend Team
Kuaishou E-commerce Frontend Team
Why Immutable Data Matters in React and How Immer Simplifies State Updates

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.name

from "cat" to "dog") does not change the object’s memory address, so

setAnimal

does 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

useImmer

hook, 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 === data

when 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

State ManagementReactHooksImmutable DataImmer
Kuaishou E-commerce Frontend Team
Written by

Kuaishou E-commerce Frontend Team

Kuaishou E-commerce Frontend Team, welcome to join us

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.