Search code examples
reactjsobservablestoremobxmobx-react

Mobx makeAutoObservable not binding this


I've built a basic counter React app with a simple MobX store. I was able to create an observable MobX status using makeObservable but for some reason when I try to use makeAutoObservable I get the error

Cannot read property 'counter' of undefined

How am I using makeAutoObservable incorrectly?

store

import { makeAutoObservable, makeObservable, action, observable } from "mobx";

class SampleStore {
  counter = 0;

  constructor(arg) {
    makeAutoObservable(this);
    // makeObservable(this, {
    //   counter: observable,
    //   increment: action.bound,
    //   decrement: action.bound,
    // });
  }

  increment() {
    this.counter++;
    return this.counter;
  }

  decrement() {
    this.counter--;
    return this.counter;
  }
}

export default SampleStore;

useStore hook

import { createContext, useContext } from "react";

import SampleStore from "./SampleStore";

export const store = {
  sampleStore: new SampleStore(),
};

export const StoreContext = createContext(store);

export const useStore = () => {
  return useContext(StoreContext);
};

provider

import { store, StoreContext } from "./stores";
import Index from "./layout/Index";

function App() {
  return (
    <StoreContext.Provider value={store}>
      <Index />
    </StoreContext.Provider>
  );
}

export default App;

React component

import { useStore } from "../stores";
import { observer } from "mobx-react";

const Index = (props) => {
  const store = useStore();
  const {
    sampleStore: { counter, increment, decrement },
  } = store;
  return (
    <>
      <h1>MobX and React.js example</h1>
      <p>{counter}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </>
  );
};

export default observer(Index);


Solution

  • It throws because your methods losing context (this) upon invocation (because you have destructured them).

    It was working with makeObservable because you were using action.bound which autobinds method to the instance context.

    If you want same functionality with makeAutoObservable you need to use arrow functions, like that:

    class SampleStore {
      counter = 0;
    
      constructor(arg) {
        makeAutoObservable(this);
      }
    
      // Make it arrow function
      increment = () => {
        this.counter++;
        return this.counter;
      }
      
      // Make it arrow function
      decrement = () => {
        this.counter--;
        return this.counter;
      }
    }
    

    Alternatively you can also use autoBind option for makeAutoObservable:

    constructor(arg) {
      makeAutoObservable(this, {}, { autoBind: true });
    }