unadlib / mutative

Efficient immutable updates, 2-6x faster than naive handcrafted reducer, and more than 10x faster than Immer.
http://mutative.js.org/
MIT License
1.53k stars 16 forks source link

Is there a way to make mutative ignore a property? #44

Closed waynebloss closed 5 days ago

waynebloss commented 5 days ago

I tried hiding a property in my baseState object by naming it with a Symbol.

const SECRET = Symbol('__MY_SECRET__');
const baseState = { a: { name: "a" }, [SECRET]: "ignore-me" };
const [draft, finalize] = create(baseState, { enablePatches: true });
mutator(draft);
const [newState, patches] = finalize();

And this would work in most cases because the mutator would not have access to SECRET.

However, I have a specific case where the mutator will have the symbol and will modify draft[SECRET]. So I'm wondering if there is any way that I can get mutative to ignore a property of baseState without mapping it onto a new object?

waynebloss commented 5 days ago

The only way I can think of is to filter any changes out of patches before JSON.stringifying them and sending them to be applied. I can't use Symbol properties without doing this anyway because JSON.stringify will make the symbol
"path": [null].

unadlib commented 5 days ago

hi @waynebloss, Mutative supports mutable and immutable markers.

You can do it like this,

const SECRET = Symbol('__MY_SECRET__');
const baseState = { a: { name: 'a' }, [SECRET]: 'ignore-me' };
const [draft, finalize] = create(baseState, {
  mark: (target, { mutable }) => {
    if (target === baseState[SECRET]) return mutable;
  },
});
mutator(draft); // baseState[SECRET] is mutable.
const newState = finalize();

This means that the value of a Symbol key is mutable, which is probably not what you intended.


If I understand correctly, you can reassign the original Symbol in the new state.

For example,

const SECRET = Symbol('__MY_SECRET__');
const baseState = { a: { name: 'a' }, [SECRET]: 'ignore-me' };
const [draft, finalize] = create(baseState);
mutator(draft);
const newState = finalize();
newState[SECRET] = baseState[SECRET];
waynebloss commented 5 days ago

All of my calls to create are going to be with enablePatches: true. I am trying to avoid Symbol paths in the resulting patches.

This is what I'm trying to avoid:

patches = [
  { op: 'replace', path: [ Symbol(__MY_SECRET__) ], value: 'new-value' }
]

This patch does not work after you run it through JSON.stringify since Symbol(__MY_SECRET__) becomes null.

I can fix the problem by doing this:

const SECRET = Symbol('__MY_SECRET__');
const baseState = { a: { name: 'a' }, [SECRET]: 'ignore-me' };
const tempStateForMutative = { a: baseState.a };
const [draft, finalize] = create(baseState, { enablePatches: true });
mutator(draft);
const [newState, patches] = finalize();

However, I'm trying to avoid making a copy of baseState. Ideally I would like an option for create to ignore any changes made to a given property of baseState. For example: create(baseState, { ignore: [SECRET] });.

waynebloss commented 5 days ago

I am not sure if what I was requesting here is actually what I need. So I will close this issue for now. Thanks!

unadlib commented 3 days ago

Out of curiosity, in your requirement you modify the value of a Symbol during the drafting process, even though the value of a Symbol is immutable, and this modification does not take effect in the final version.

What does it mean to modify it during the drafting process? Or does it have any other use?

waynebloss commented 3 days ago

I was simply trying to avoid creating patches for a specific property. When I create patches, I am sending them to another process to apply them. I have some cached meta-data on each of my local data nodes that I don't want to create patches for.