The getter method of the makeAutoObservable object returns wrong value.
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",
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
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
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?
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;
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.
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