-
Notifications
You must be signed in to change notification settings - Fork 26
Description
Description
When calling create() on an object that is already a Mutative draft proxy, the resulting object's nested properties that weren't modified in the recipe share references with the original base object, not the draft's current state.
This allows accidental mutation of the original object, which violates immutability expectations.
Minimal Reproduction
import { create } from 'mutative';
interface Metadata {
value: string;
}
interface Node {
name: string;
metadata: Metadata;
}
interface Tree {
nodes: Node[];
}
// Helper function that uses create() internally - common pattern for reusable transformations
function updateName(node: Node, newName: string): Node {
return create(node, (draft) => {
draft.name = newName;
});
}
const original: Tree = {
nodes: [
{ name: 'a', metadata: { value: '' } }
]
};
const result = create(original, (draft) => {
draft.nodes = draft.nodes.map((node) => {
// Calling a helper that uses create() - doesn't look like nested create()
const updated = updateName(node, 'modified');
// BUG: updated.metadata shares reference with original
updated.metadata.value = 'oops';
return updated;
});
});
console.log('Original mutated?', original.nodes[0].metadata.value === 'oops'); // true!Note: In real code, the nested create() call is typically hidden inside a helper function (like updateName above), making it non-obvious that you're calling create() on a draft element.
Expected Behavior
When create() is called on a draft element, the resulting object should not share mutable references with the original base object. Either:
- Nested
create()should work correctly (like Immer handles nestedproduce()) - Or an error/warning should be thrown indicating this pattern isn't supported
Actual Behavior
The object returned from the inner create() call has nested properties (metadata) that reference the original base object. Mutating these nested properties unexpectedly mutates the original.
Environment
- mutative version: latest
- Node.js version: v20+
Context
We discovered this while migrating from Immer to Mutative. Our codebase uses a visitor pattern where helper functions call create() to modify parts of a tree, and these helpers are sometimes invoked from within a create() recipe. This pattern worked correctly with Immer but causes original tree mutation with Mutative.