mobxjs / mobxjs/mobx

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

Open
#3,917 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

❔ question
Dominant language
TypeScript
Stars
28.2k
Forks
1.8k
Avg merge
1h 38m
Merged PRs (30d)
1

Description

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

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:

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:

// 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):

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

Actual outcome:

Reference is different every time:

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

How to reproduce the issue:

Here is the example code to reproduce:

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the supplied TypeScript reproduction and the makeAutoObservable entry point, then trace how store.value is assigned and read twice. Compare parentCache and objectCache with the current behavior; done means determining whether identity can be preserved and establishing the resulting behavior or workaround.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
frontend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.