Skip to content

Instantly share code, notes, and snippets.

@softmarshmallow
Created October 2, 2025 07:09
Show Gist options
  • Select an option

  • Save softmarshmallow/04da740e3b48b183a4961da20f33af3c to your computer and use it in GitHub Desktop.

Select an option

Save softmarshmallow/04da740e3b48b183a4961da20f33af3c to your computer and use it in GitHub Desktop.
demonstrates how immer produce with patches work with nested produce
import { produce, produceWithPatches, enablePatches } from "immer";
enablePatches();
describe("Immer nested produce with produceWithPatches", () => {
const initialState = { name: "John", age: 30 };
it("should capture direct changes in patches", () => {
const [result, patches] = produceWithPatches(initialState, (draft) => {
draft.name = "Jane";
draft.age = 31;
});
expect(result.name).toBe("Jane");
expect(result.age).toBe(31);
expect(patches).toHaveLength(2);
expect(patches).toContainEqual({
op: "replace",
path: ["name"],
value: "Jane",
});
expect(patches).toContainEqual({ op: "replace", path: ["age"], value: 31 });
});
it("should NOT capture nested produce changes unless assigned back", () => {
const [result, patches] = produceWithPatches(initialState, (draft) => {
// This nested produce doesn't affect the parent draft
produce(draft, (nested) => {
nested.name = "Nested";
nested.age = 99;
});
// Only this direct change is captured
draft.name = "Parent";
});
expect(result.name).toBe("Parent");
expect(result.age).toBe(30); // unchanged
expect(patches).toHaveLength(1);
expect(patches).toContainEqual({
op: "replace",
path: ["name"],
value: "Parent",
});
});
it("should capture nested produce changes when assigned back", () => {
const [result, patches] = produceWithPatches(initialState, (draft) => {
const nested = produce(draft, (nested) => {
nested.name = "Nested";
nested.age = 99;
return nested;
});
// Assign back to parent draft - this gets captured
draft.name = nested.name;
draft.age = nested.age;
});
expect(result.name).toBe("Nested");
expect(result.age).toBe(99);
expect(patches).toHaveLength(2);
expect(patches).toContainEqual({
op: "replace",
path: ["name"],
value: "Nested",
});
expect(patches).toContainEqual({ op: "replace", path: ["age"], value: 99 });
});
it("should capture when using Object.assign (it triggers proxy)", () => {
const [result, patches] = produceWithPatches(initialState, (draft) => {
const nested = produce(draft, (nested) => {
nested.name = "Nested";
nested.age = 99;
return nested;
});
// Object.assign DOES trigger proxy assignments!
Object.assign(draft, nested);
});
expect(result.name).toBe("Nested");
expect(result.age).toBe(99);
// Object.assign triggers proxy assignments, so patches are captured
expect(patches).toHaveLength(2);
expect(patches).toContainEqual({
op: "replace",
path: ["name"],
value: "Nested",
});
expect(patches).toContainEqual({ op: "replace", path: ["age"], value: 99 });
});
it("should capture when doing draft.obj1 = produce() (assigning produce result)", () => {
const initialStateWithObj = { obj1: { name: "Original" }, age: 30 };
const [result, patches] = produceWithPatches(
initialStateWithObj,
(draft) => {
// This DOES work - assigning the result of produce() to a draft property
draft.obj1 = produce(draft.obj1, (objDraft) => {
objDraft.name = "Produced";
return objDraft;
});
}
);
expect(result.obj1).toEqual({ name: "Produced" });
expect(result.age).toBe(30);
expect(patches).toHaveLength(1);
expect(patches).toContainEqual({
op: "replace",
path: ["obj1"],
value: { name: "Produced" },
});
});
it("should capture when doing draft.obj1 = produce() with different input", () => {
const initialStateWithObj = { obj1: { name: "Original" }, age: 30 };
const [result, patches] = produceWithPatches(
initialStateWithObj,
(draft) => {
// Using a completely different input to produce()
draft.obj1 = produce({ name: "Different", age: 0 }, (objDraft) => {
objDraft.name = "From Different";
objDraft.age = 42;
return objDraft;
});
}
);
expect(result.obj1).toEqual({ name: "From Different", age: 42 });
expect(result.age).toBe(30);
expect(patches).toHaveLength(1);
expect(patches).toContainEqual({
op: "replace",
path: ["obj1"],
value: { name: "From Different", age: 42 },
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment