#3917·mobx

The same object being proxied has different references in scope of the same observable

Author: andres-kovalevCreated Sep 4, 2024Updated Sep 5, 2024
Labels❔ question

When I assign the same const object as a value of the same observable - I get different reference every time:

javascript
store.reactiveField = constObject;
const cache = store.reactiveField;
store.reactiveField = constObject; // even tho we assign the same object, we got different reference
console.log(cache === store.reactiveField); // false
...

I understand store.reactiveField and constObject are different objects (the first one is a proxy for the second one), but it still looks counterintuitive and error prone to get a new reference every time. For instance, when this proxy is used as a dependency for a react hook:

javascript
useEffect(() => {
  // this effect is triggered every time I do `store.reactiveField = constObject`
}, [store.reactiveField]);

Is it possible to preserve the reference in a case when the actual proxy target has the same reference?

If there is a reason for such behavior, is there any workaround for that? At the moment the only idea I have is to keep the copy of the original (non-proxied) value and perform deep assign manually:

javascript
// without safety checks for simplicity
function deepAssign(reactive, original, value) {
  Object.entries(value).forEach(([ key, value ]) => {
    if (original[key] !== value) {
      reactive[key] = value;
      return;
    }

    if (typeof value !== 'object') {
      return;
    }

    deepAssign(reactive[key], original[key], value);
  });
}

Intended outcome:

I expect the same reference can be reused (since the underlying value has not changed):

javascript
console.log(cache === store.reactiveField); // true

Actual outcome:

Reference is different every time:

javascript
console.log(cache === store.reactiveField); // false

How to reproduce the issue:

Here is the example code to reproduce:

typescript
type TestObject = { number: number };
type Parent = { object: TestObject };

const object = { number: 0 };
const parent = { object };

class Store {
  value: ParentObject = {}

  constructor() {
    makeAutoObservable(this);
  }
}

const store = new Store();
store.value = parent;
const parentCache = store.value;
const objectCache = store.value.object;
store.value = parent;

console.log(parentCache === store.value); // false
console.log(objectCache === store.value.object); // false

Versions

^6.13.1