Reputation: 107
I've been fiddling around with MobX and am struggling to understand why the ChildComponent
in the following code snippet doesn't react to state updates.
My understanding is that the ChildComponent
should update since isOpen.get()
changes after every call to controller.updateState()
(which is brokered through the button click). I added some log statements and found that the state does update correctly, but the text inside the ChildComponent
doesn't and I haven't been able to figure out why. I would really appreciate any help in understanding what I've done wrong here.
Note: I am aware that a call to makeObservable
is required as of MobX v6. However, the codebase that I'm working on uses that older version that doesn't support that function call.
App.tsx:
import { computed } from "mobx";
import { observer } from "mobx-react";
import { Controller } from "./controller";
const ChildComponent = observer(({ isOpen }: { isOpen: boolean }) => (
<p>isOpen: {isOpen.toString()}</p>
));
export default function App() {
const controller = new Controller();
const isOpen = computed(() => controller.state === "open");
const onClick = () => {
if (isOpen.get()) {
controller.updateState("closed");
} else {
controller.updateState("open");
}
};
return (
<div className="App">
<ChildComponent isOpen={isOpen.get()} />
<button onClick={onClick}>Toggle state</button>
</div>
);
}
controller.ts:
import { action, observable } from "mobx";
type State = "open" | "closed";
export class Controller {
@observable.ref
state: State;
constructor() {
this.state = "open";
}
@action
updateState = (newState: State) => {
this.state = newState;
};
}
Link to codesandbox: https://codesandbox.io/s/broken-pine-3kwpt?file=/src/App.tsx
Upvotes: 0
Views: 2354
Reputation: 18566
Lots of things goes wrong there:
Use makeObservable
or downgrade example to MobX version 5, otherwise it won't work
You don't need to use @observable.ref
for boolean, just use plain regular @observable
You can't use computed
like that. computed
is a decorator that should be used inside your store, similar to observable
, like that:
@computed
get isOpen() {
return this.state === 'open';
}
In your example App
should be wrapped in observer
because it dereferences an observable value (isOpen
). And every component that does it should be wrapped. At the same time ChildComponent
gets isOpen
prop as primitive value so it does not benefit from being observer
(because it does not reference any observable property).
You need to create your controller differently. Right now you recreate it on every render and even if you fix all the problems above it won't work because every time you change some value the App
will rerender and recreate the store with default values.
Hope it makes sense!
Working Codesandbox example with everything fixed
Upvotes: 2