-
|
We have a set of JavaScript objects that have circular references and cross-references. We use Symbol keys for these references so that they're skipped by normal object key iteration; that lets them work with code like const Parent = Symbol('parent');
const Owner = Symbol('owner');
const entity = {
name: 'Entity A',
children: []
};
entity.children.push({ name: 'Entity A1', [Parent]: entity });
entity.children.push({ name: 'Entity A2',, [Parent]: entity });
const obj = {
entity,
items: [
{ name: 'Item for Entity A1', [Owner]: entity.children[0] },
{ name: 'Item for Entity A2', [Owner]: entity.children[1] },
]
};In the past, this use of Symbol keys worked with Immer (because Immer skipped symbol keys), but recent updates to Immer have changed things. I can't complain about this, because this usage is unsupported by Immer, but I'm looking at Mutative as an alternative solution. I have four questions. First, are circular references generally and officially supported? (Based on #8, it looks like the answer is no, but I didn't see this addressed in the docs.) Second, are non-unidirectional trees (i.e., trees with more than one path from the root to a tree node) generally supported? Third, if circular references and non-unidirectional trees aren't generally, officially supported, then are they at least expected to work with non-enumerable (symbol) keys? In my experiments, using Mutative passes my test suite, so the answer is at least "Somewhat yes", but which of the following is it? a. It's purely by chance that this works. It will likely fail in the future. I should not use Mutative like this. Fourth, can the mark feature help here? I admit I'm having a little trouble understanding marking from reading that guide. If I understand correctly, it might help, although I would need to know the object's key (to distinguish between the symbol-keyed cross-references and the "real" objects) as well as the value. Could that be added as an enhancement to Mutative? Thanks, and sorry for all the questions. I've enjoyed reading about Mutative - very impressive work. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
|
Hi, thanks a lot for the detailed write-up and the concrete example – this is exactly the kind of context that makes these questions much easier to reason about. Let me go through your questions one by one. 1. Circular referencesMutative conceptually treats the state it manages as a tree / DAG, not as a general object graph with arbitrary cycles. Circular references are not something we aim to support as a feature. When
Important detail for your use case:
So from Mutative’s point of view, your state is still “supposed to be” acyclic – it’s just that the dev-time guardrails don’t see the loops encoded purely via symbols yet. 2. Non-unidirectional trees (shared nodes / multiple parents)Having more than one path from the root to the same node without introducing a cycle (i.e. a DAG with shared nodes) is something Mutative handles, but with an important caveat: Clarification: When the same object is referenced from multiple paths in the base state, Mutative creates independent drafts for each path. This means: const obj = {};
obj.color1 = obj.color2 = { name: 'Red' };
const result = create(obj, (draft) => {
draft.color1 === draft.color2; // false - different drafts!
draft.color1.name = 'Blue';
draft.color2.name; // still 'Red'
});
result.color1 === result.color2; // false
// { color1: { name: 'Blue' }, color2: { name: 'Red' } }This behavior is by design and matches Immer's behavior (see Immer's test: "supports a base state with multiple references to an object"). If you need to preserve or create shared references in the result, you can do so explicitly: const result = create(obj, (draft) => {
draft.color1.name = 'Blue';
draft.color2 = draft.color1; // Explicitly share
});
// Now result.color1 === result.color2 ✅So:
In your example, if we ignore the symbol-keyed links, the structure that Mutative "sees" is a regular tree: 3. Symbol keys, circular references, and how “supported” this isMutative does support
That’s a deliberate design choice – we want Mutative to behave like plain JavaScript objects as much as possible here. In your particular pattern (back-references stored under
Given the three options you listed:
The most accurate description is:
More concretely:
So I’d recommend treating your current pattern as:
If we make that kind of change, it would be clearly called out in the changelog as a behavioral change. 4. Can
|
Beta Was this translation helpful? Give feedback.
Hi, thanks a lot for the detailed write-up and the concrete example – this is exactly the kind of context that makes these questions much easier to reason about.
Let me go through your questions one by one.
1. Circular references
Mutative conceptually treats the state it manages as a tree / DAG, not as a general object graph with arbitrary cycles. Circular references are not something we aim to support as a feature.
When
enableAutoFreezeis turned on, Mutative will in development mode:Important detail for your use case:
the current circular-reference checker only follows “regular” keys (strin…