5

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);

Jon_B
  • 969
  • 4
  • 16
  • 24

3 Answers3

6

You can also use autoBind: true in options of makeAutoObservable:

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

This will automatically bind all your actions - so you won't loose the context on destructuring

4

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;
  }
}
Danila
  • 15,606
  • 2
  • 35
  • 67
0

Try not to destructure the store like that. When destructuring, any primitive variables will remain at the latest values and won't be observable anymore. You will also need to bind the click methods back to the store.

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

const Index = (props) => {
  const {sampleStore:store} = useStore();

  return (
    <>
      <h1>MobX and React.js example</h1>
      <p>{store.counter}</p>
      <button onClick={store.increment.bind(store)}>+</button>
      <button onClick={store.decrement.bind(store)}>-</button>
    </>
  );
};

export default observer(Index);
Ivan V.
  • 7,593
  • 2
  • 36
  • 53
  • `When destructuring, any primitive variables will remain at the latest values and won't be observable anymore.` this is not true, in that case it does not matter when you destructure, because your whole component is observer already, any destructure will be picked up. It would be true if you wanted to assign new values, but if you just need to observe then it does not matter – Danila Jul 12 '21 at 20:32
  • 1
    @Danila you are right about the `store.count` my bad. – Ivan V. Jul 12 '21 at 21:19