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

deepObserve not working with ObservableSet

Open gioragutt opened this issue 4 years ago • 4 comments

repro:

  • repro.mjs
import {observable, runInAction} from 'mobx';
import {deepObserve} from 'mobx-utils';

const set = observable.set([1, 2, 3]);
const obj = observable({bla: true});

deepObserve(set, (change, path, root) => console.log(change, path, root));
deepObserve(obj, (change, path, root) => console.log(change, path, root));

console.log(set);

runInAction(() => {
  set.add('bla');
  obj.bla = false;
})

console output:

ObservableSet [Set] {
  name_: 'ObservableSet@1',
  data_: Set(3) { 1, 2, 3 },
  atom_: Atom {
    name_: 'ObservableSet@1',
    isPendingUnobservation_: false,
    isBeingObserved_: false,
    observers_: Set(0) {}, // <<<<<<<
    diffValue_: 0,
    lastAccessedBy_: 0,
    lowestObserverState_: 2,
    onBOL: undefined,
    onBUOL: undefined
  },
  changeListeners_: undefined,
  interceptors_: undefined,
  dehancer: undefined,
  enhancer_: [Function (anonymous)],
  [Symbol(mobx administration)]: {}
}

{
  type: 'update',
  observableKind: 'object',
  debugObjectName: 'ObservableObject@2',
  object: { bla: [Getter/Setter] },
  oldValue: true,
  name: 'bla',
  newValue: false
}  { bla: [Getter/Setter] }

Notice how in console.log(set) it has no observers.

gioragutt avatar Jun 27 '21 14:06 gioragutt

Proposed change:

https://github.com/mobxjs/mobx-utils/blob/master/src/deepObserve.ts#L101

function observeRecursively(thing: any, parent: Entry | undefined, path: string) {
    if (isRecursivelyObservable(thing)) {
        const entry = entrySet.get(thing)
        if (entry) {
            if (entry.parent !== parent || entry.path !== path)
                // MWE: this constraint is artificial, and this tool could be made to work with cycles,
                // but it increases administration complexity, has tricky edge cases and the meaning of 'path'
                // would become less clear. So doesn't seem to be needed for now
                throw new Error(
                    `The same observable object cannot appear twice in the same tree,` +
                        ` trying to assign it to '${buildPath(parent)}/${path}',` +
                        ` but it already exists at '${buildPath(entry.parent)}/${entry.path}'`
                )
        } else {
            const entry = {
                parent,
                path,
                dispose: observe(thing, genericListener),
            }
            entrySet.set(thing, entry)
            entries(thing).forEach(([key, value]) => observeRecursively(value, entry, key))
        }
    // the else is added.
    } else if (isObservableSet(thing)) {
        const entry = {
            parent,
            path,
            dispose: observe(thing, genericListener),
        }
        entrySet.set(thing, entry)
    }
}

Tested and works on:

import {observable, runInAction} from 'mobx';
import {deepObserve} from 'mobx-utils';

const set = observable.set([1, 2, 3]);
deepObserve(set, (change, path, root) => console.log(change, path, root));
runInAction(() => set.add('bla'));

At first, I didn't add the isObservableSet(thing) check, and the following code failed:

import {observable, runInAction} from 'mobx';
import {deepObserve} from 'mobx-utils';

const obj = observable({
  bla: true,
  set: observable.set([1, 2, 3])
});

deepObserve(obj, (change, path, root) => console.log(change, path, root));
runInAction(() => obj.set.add('bla'));

With the following error:

/Users/gioraguttsait/Git/some-repo/node_modules/mobx/dist/mobx.cjs.development.js:91
    throw new Error("[MobX] " + e);
          ^

Error: [MobX] Cannot obtain administration from true
    at die (/Users/gioraguttsait/Git/some-repo/node_modules/mobx/dist/mobx.cjs.development.js:91:11)
    at getAdministration (/Users/gioraguttsait/Git/some-repo/node_modules/mobx/dist/mobx.cjs.development.js:5551:3)
    at observeObservable (/Users/gioraguttsait/Git/some-repo/node_modules/mobx/dist/mobx.cjs.development.js:3157:10)
    at Object.observe (/Users/gioraguttsait/Git/some-repo/node_modules/mobx/dist/mobx.cjs.development.js:3153:118)
    at observeRecursively (/Users/gioraguttsait/Git/some-repo/node_modules/mobx-utils/mobx-utils.umd.js:1195:35)
    at /Users/gioraguttsait/Git/some-repo/node_modules/mobx-utils/mobx-utils.umd.js:1188:32
    at Array.forEach (<anonymous>)
    at observeRecursively (/Users/gioraguttsait/Git/some-repo/node_modules/mobx-utils/mobx-utils.umd.js:1186:41)
    at deepObserve (/Users/gioraguttsait/Git/some-repo/node_modules/mobx-utils/mobx-utils.umd.js:1210:9)
    at file:///Users/gioraguttsait/Git/some-repo/mobile/test.mjs:9:1

gioragutt avatar Jun 27 '21 14:06 gioragutt

Another proposed solution:

https://github.com/mobxjs/mobx-utils/blob/master/src/deepObserve.ts#L32

function isRecursivelyObservable(thing: any) {
    return isObservableObject(thing) || isObservableArray(thing) || isObservableMap(thing) || isObservableSet(thing);
}

After thinking about it, an ObservableSet can also be recursively observable (since it can hold observables). Therefore it shouldn't be treated differently from the other types.

Needless to say that code that failed before now works

gioragutt avatar Jun 27 '21 15:06 gioragutt

Does it work for non-stringable entries? Set's values are also it's keys, so I wonder what happens with path...

urugator avatar Jun 28 '21 08:06 urugator

Hmmm good point, I think that the entries in a Set are the items themselves as well, so that means that the path will probably be a toString of the observable item 🤭 I'll see how it looks

gioragutt avatar Jun 28 '21 11:06 gioragutt