Shrinking an array with `length =` and growing it back loses the patches for the truncated indices

Open Beginner friendly
#1,355 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 4 days

@RClaudiuM is already working on this.

Since Nov 7, 2026.

  • #1356 by @RClaudiuM — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
75/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript
Domain
backend

Research direction

Start in src/plugins/patches.ts, especially generateArrayPatches_, and trace how shrinking a draft array changes copy_ and assigned_. Add the provided regression test for shrinking and regrowing length, then run the patch-related tests. Done means forward patches reproduce the recipe result and inverse patches restore the base.

Written by the indexing model from the issue text.

Description

🐛 Bug Report

With enablePatches(), if a recipe shrinks a draft array by assigning .length and then grows it back to at least its original length, no replace patch (and no inverse patch) is produced for the truncated indices. Replaying the patches on the base gives a different state than the recipe, and the inverse patches do not restore the base. The same recipe written with splice(0) produces correct patches.

Link to repro

Not applicable: no CodeSandbox account; the script below and the unit test at the end reproduce it.

To Reproduce

npm i [email protected], save as t.mjs, run node t.mjs:

import { produceWithPatches, applyPatches, enablePatches } from "immer";
enablePatches();

const base = [1];
const [result, patches, inverse] = produceWithPatches(base, d => {
  d.length = 0; // truncate index 0
  d[1] = 2;     // grow again
});

console.log(result);
console.log(patches);
console.log(inverse);
console.log(applyPatches(base, patches));
console.log(applyPatches(result, inverse));

const [r2, p2, i2] = produceWithPatches([1, 2], d => { d.length = 0; d.length = 2; });
console.log(r2, p2, i2);

Observed behavior

[ <1 empty item>, 2 ]
[ { op: 'add', path: [ 1 ], value: 2 } ]
[ { op: 'remove', path: [ 1 ] } ]
[ 1, 2 ]
[ <1 empty item> ]
[ <2 empty items> ] [] []

applyPatches(base, patches) keeps the stale 1 at index 0, and applyPatches(result, inverse) does not bring it back. The second recipe changes both elements but produces no patches at all.

Expected behavior

The same patches as for d.splice(0); d[1] = 2, which immer already gets right:

[
  { op: 'replace', path: [ 0 ], value: undefined },
  { op: 'add', path: [ 1 ], value: 2 }
] [
  { op: 'replace', path: [ 0 ], value: 1 },
  { op: 'remove', path: [ 1 ] }
]

so that applyPatches(base, patches) gives [ undefined, 2 ] and applyPatches(result, inverse) gives [ 1 ].

generateArrayPatches_ in src/plugins/patches.ts only emits a replace for an index below base.length when assigned_.get(i) is set (or allIndicesReassigned_). Assigning a smaller length truncates copy_ without marking the dropped indices as assigned, so they are skipped once the array is long again.

Unit test:

test("length shrink then grow produces replace patches", () => {
  const base = [1]
  const [result, patches, inverse] = produceWithPatches(base, d => {
    d.length = 0
    d[1] = 2
  })
  expect(applyPatches(base, patches)).toEqual(result)
  expect(applyPatches(result, inverse)).toEqual(base)
})

Environment

We only accept bug reports against the latest Immer version.

  • Immer version: 11.2.9 (also main 75a290e, built from source), Node.js v26.13.0
  • I filed this report against the latest version of Immer
  • Occurs with setUseProxies(true)
  • Occurs with setUseProxies(false) (ES5 only)

Credit: https://ucsbplse.github.io/

Dominant language
JavaScript
Stars
29k
Forks
905
Avg merge
3d 16h
Merged PRs (30d)
22

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from immerjs/immer

All issues in immerjs/immer

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.