mobx icon indicating copy to clipboard operation
mobx copied to clipboard

The getter method of the makeAutoObservable object returns wrong value.

Open davincerepo opened this issue 5 months ago β€’ 6 comments

Intended outcome:

Expect the get method to return the field value.

Actual outcome:

It looks like the cached value from the last get call is returned.

How to reproduce the issue:


class TestStore {
    private _source?: string

    constructor() {
        makeAutoObservable(this)
    }

    do(val: any) {
        this.source // To reproduce the problem you must add this line
        this.source = val
        console.log(`TestStore: do, after ${JSON.stringify(this.source)}, after 2 ${JSON.stringify(this._source)}, eq ${this.source === this._source}`);
    }

    get source() {
        return this._source;
    }

    set source(val: string | undefined) {
        this._source = val;
        // Remove this line of log, the do method still has problems
        console.log(`TestStore: set source ${JSON.stringify(val)}, after ${JSON.stringify(this.source)}, after 2 ${JSON.stringify(this._source)}, eq ${this.source === this._source}`);
    }
}

let test = new TestStore()
test.do('123')

Actual log: LOG TestStore: set source "123", after undefined, after 2 "123", eq false LOG TestStore: do, after undefined, after 2 "123", eq false

You can see that this._source and this.source return different values, but source is just a simple get method without any other logic. This result is counterintuitive and may be a serious problem.

Printing the log in the setter to access the getter does not affect the result. This is not the root cause of the problem. Even if this line of code is removed, the updated value cannot be obtained in the do method.

Versions There are problems with both versions.

"mobx": "6.12.3",
"mobx-react-lite": "4.0.7",

"mobx": "6.13.7",
"mobx-react-lite": "4.1.0",

davincerepo avatar Jul 08 '25 13:07 davincerepo

I am now faced with refactoring a lot of code

  • I cannot mark getter/setter methods as observable or action, mobx does not allow it
  • I need to monitor the modification in setter method. If the field configuration is false, there is no caching problem and it cannot be monitored
  • I tested configure the getter/setter with computed({ keepAlive: false }), but it still cannot read the latest value

It seems that the only way is to refactor the code and change the getter method to getXXX, but there is a lot of code to change. Is there any other way? Thank you very much for your suggestions

davincerepo avatar Jul 09 '25 00:07 davincerepo

Maybe I mistyped the tag, this is not a bug, but this result is unexpected, I expected to modify a field and read it to see the modified value.

configure({
    enforceActions: "never"
});

I configured this globally and it allows you to modify field values ​​in setter methods without being warned, which seems wrong, I should have avoided getters and setters in the first place and used plain setXXX / getXXX

davincerepo avatar Jul 09 '25 00:07 davincerepo

Sorry I have a little trouble following the full context and setup through multiple messages and without having all the code. Would you mind creating the reproduction in a codesandbox or other online playground so that we're working of the same baseline?

mweststrate avatar Jul 09 '25 01:07 mweststrate

Sorry I have a little trouble following the full context and setup through multiple messages and without having all the code. Would you mind creating the reproduction in a codesandbox or other online playground so that we're working of the same baseline?

Sure, please check this

https://codesandbox.io/p/sandbox/8w8y43

import { configure, makeAutoObservable } from "mobx";
import { useState } from "react";
import "./styles.css";

configure({
  enforceActions: "never",
});

class TestStore {
  private _source?: string; //New discovery: If you set the initial value of _source to null, the problem will not occur

  constructor() {
    makeAutoObservable(this);
  }

  do(val: any) {
    this.source; // To reproduce the problem you must add this line
    this.source = val;
    return this.source; // The problem is: this.source !== val
  }

  get source() {
    return this._source;
  }

  set source(val: string | undefined) {
    this._source = val;
  }
}

let test = new TestStore();

const App = () => {
  const [msg, setMsg] = useState<string | undefined>();

  const handleClick = () => {
    let val = Date.now() + "";
    let ret = test.do(val);
    setMsg(`passed val ${val}, return val ${ret}, equals ${val === ret}`);
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={handleClick}>Click me</button>
      <p>{msg}</p>
    </div>
  );
};

export default App;

davincerepo avatar Jul 09 '25 02:07 davincerepo

The example looks contrived. You're reading from a computed observable (this.source) before setting it, thus making MobX queuing reactions or caching the value. MobX tries to ensure consistency by evaluating reactions asynchronously. Hence you see what you see.

Basically, you're misusing the tool.

fourhtyoz avatar Nov 08 '25 20:11 fourhtyoz

The example looks contrived. You're reading from a computed observable (this.source) before setting it, thus making MobX queuing reactions or caching the value. MobX tries to ensure consistency by evaluating reactions asynchronously. Hence you see what you see.

Basically, you're misusing the tool.

Thank you. I don't need MobX's caching, so should I use getXXX instead of getter? I used getter because its syntax is more concise. @fourhtyoz

davincerepo avatar Nov 20 '25 14:11 davincerepo