Search code examples
observablemobxmobx-reactreact-typescript

MobX observable not being tracked


I'm fairly new to using MobX and have been trying to use it to detect window resize events in my React-Typescript app. Here's my code:

App.tsx:

import { computed } from "mobx";
import { observer } from "mobx-react";
import type { Breakpoint } from "./display_size_observer";
import { DisplaySizeObserver } from "./display_size_observer";
import "./styles.css";

const ChildComponent = ({ size }: { size: Breakpoint }) => {
  return <p>Size: {size}</p>;
};

export default function App() {
  const displaySize = computed(() => DisplaySizeObserver.size);
  const Child = observer(() => <ChildComponent size={displaySize.get()} />);
  return <Child />;
}

display_size_observer.ts:

import { observable, action } from "mobx";

export type Breakpoint = "small" | "medium" | "large";

const BreakpointSmall: number = 375;
const BreakpointMedium: number = 768;

const getSizeAsBreakpoint = (size: number) => {
  if (size <= BreakpointSmall) {
    return "small";
  } else if (size <= BreakpointMedium) {
    return "medium";
  }
  return "large";
};

const getWindowSize = () => {
  return getSizeAsBreakpoint(window.innerWidth);
};

const DisplaySizeObserverFactory = (() => {
  let instance: DisplaySizeObserver;

  class DisplaySizeObserver {
    @observable.ref
    size: Breakpoint = getWindowSize();

    constructor() {
      window.addEventListener("resize", this.handleResize);
    }

    @action
    private handleResize() {
      const newSize = getWindowSize();
      if (!this.size || this.size !== newSize) {
        this.size = newSize;
        console.log("Updating size to:", this.size);
      }
    }
  }

  return {
    getInstance: () => {
      if (!instance) {
        instance = new DisplaySizeObserver();
      }
      return instance;
    }
  };
})();

export const DisplaySizeObserver = DisplaySizeObserverFactory.getInstance();

Link to codesandbox: https://codesandbox.io/s/admiring-mendel-h0kcb?file=/src/display_size_observer.ts:0-1169&resolutionWidth=446&resolutionHeight=675

According to my understanding, having the ChildComponent within an observer should track the update to the display size and re-render the Child accordingly. However, this doesn't seem to be happening.

I would appreciate any help in understanding what I've done wrong and how to fix it.


Solution

  • Several things goes wrong:

    1. add makeObservable(this); inside class constructor. It is required since MobX v6. More info: mobx-react observer don't fires when I set observable

    2. handleResize function losing context here: window.addEventListener('resize', this.handleResize);, because it is not bound nor arrow function. Make it arrow function.

    3. Don't use @observable.ref for size, just @observable is enough

    4. Every component that uses observable values should be wrapped into observer HOC. Don't do this wierd things you do in App, just rewrite it like that:

    const ChildComponent = observer(() => {
      return <p>Size: {DisplaySizeObserver.size}</p>;
    });
    
    export default function App() {
      return <ChildComponent />;
    }
    

    Or if you want ChildComponent to accept props then wrap App inside observer and just pass props:

    const App = observer(() => {
      return <ChildComponent size={DisplaySizeObserver.size} />;
    })
    
    export default App
    

    Working example on Codesandbox