mobx-keystone icon indicating copy to clipboard operation
mobx-keystone copied to clipboard

Patch generated when setting equivalent valueType models

Open kcoop opened this issue 2 years ago • 8 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?

kcoop avatar Jun 11 '22 17:06 kcoop

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

xaviergonz avatar Jun 12 '22 21:06 xaviergonz

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.

kcoop avatar Jun 12 '22 21:06 kcoop

(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)

kcoop avatar Jun 12 '22 21:06 kcoop

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?

xaviergonz avatar Jun 12 '22 21:06 xaviergonz

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?

kcoop avatar Jun 12 '22 21:06 kcoop

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.

xaviergonz avatar Jun 12 '22 21:06 xaviergonz

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

xaviergonz avatar Jun 12 '22 21:06 xaviergonz

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...

kcoop avatar Jun 12 '22 22:06 kcoop