Created
October 2, 2025 07:09
-
-
Save softmarshmallow/04da740e3b48b183a4961da20f33af3c to your computer and use it in GitHub Desktop.
demonstrates how immer produce with patches work with nested produce
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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