xaviergonz / mobx-keystone

A MobX powered state management solution based on data trees with first class support for Typescript, support for snapshots, patches and much more

Home Page:https://mobx-keystone.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Patch generated when setting equivalent valueType models

kcoop opened this issue · comments

It seems like models with valueType: true are meant to be thought of like structs, and as such one would expect setting a valueTyped prop to a new model with the same prop values not to generate a patch, since the value has not really changed. But the following test fails:

import { model, Model, Patch, patchRecorder, prop } from 'mobx-keystone';

@model('app/CurrencyAmount')
class CurrencyAmount extends Model(
  {
    amount: prop<number>(),
    currency: prop<string>('USD')
  },
  { valueType: true }
) {}

@model('/app/TestContainer')
class TestContainer extends Model({
  balance: prop<CurrencyAmount>(() => new CurrencyAmount({ amount: 0, currency: 'USD' })).withSetter()
}) {}

describe('Patch generation', () => {
  it('should not emit a patch when assigning a valueType with equal values', () => {
    const container = new TestContainer({});
    let patchOccurred = false;
    const recorder = patchRecorder(container, {
      onPatches: (newPatches: Patch[], inversePatches: Patch[]) => {
        patchOccurred = true;
      }
    });

    container.setBalance(new CurrencyAmount({ amount: 0, currency: 'USD' }));
    expect(patchOccurred).toBe(false);
    recorder.dispose();
  });
});

This is problematic for diffing two snapshots - the diff shows erroneous non changes.

Looking at the stack, mobx-keystone generates a patch without comparing the props of the models. Wondering if there should be a special case test in the 'update' case of objectDidChange for models that have valueType: true?

Is this about the patches being generated by applySnapshot over another node being "do nothing" patches for value types?

Yes, exactly, that's the issue. They are do nothing patches, which would be confusing to users when presented as part of a diff. I can of course filter them out externally if necessary, but it seems like an upstream fix might be more suitable.

(They also add a little weight to the 'versioned models' in this app, where versions are implemented as a collection of patches with a base model)

I can make applySnapshot over valueTypes not generate patches if nothing changed, but your unit test would still fail (since mobx-keystone assumes a value changed when you are assigning a whole new instance created via "new" to avoid expensive deep checking). Would that solve your issue?

That would be great! But I don't understand how that test (which was really only for debugging purposes) would then fail - wouldn't no patch be generated?

A patch is always generating when doing this:

prop x = new Model(...)

no matter its props

What I'll change is how value types behave. Right now they always generate a clone (new X...) before being assigned to a prop. What I'll change is that the clone will only be generated if the model has a previous parent. If it has no parent (such as the case when using applySnapshot) it won't use clone, therefore it won't internally use "new X..." and not assume it is a whole new model instance, so that it can be properly reconciled.

Should be now fixed in v0.69.1 (the applySnapshot case as discussed above)

Thanks, confirmed, that does fix my issue. Appreciate the quick turnaround!

Not critical, but when thinking of valueTypes like structs, it will still be slightly confusing if a patch is generated by assigning a new equivalent value (e.g. setting a point with the same x,y, or a new color with the same RGB value).

I understand the perf concern though, and maybe there's some other semantic integrity issue I'm missing, but I wonder if an identity comparison of the props would be worth it? (of course only for valueTypes)

Or maybe it's wrong to think of these like structs...