nugget_boi
nugget_boi

Reputation: 107

MobX Observer component not reacting to change in Observable

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

Answers (1)

Danila
Danila

Reputation: 18566

Lots of things goes wrong there:

  1. Use makeObservable or downgrade example to MobX version 5, otherwise it won't work

  2. You don't need to use @observable.ref for boolean, just use plain regular @observable

  3. 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';
  }
  1. 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).

  2. 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

Related Questions