5

I am currently writing a React App, that should be able to rerender components when the value of any observable changes. The problem is, that I can't get email to rerender if it changes.

store.ts

export class ExampleStore {
  @observable email = 'hello';

  @action setEmail(email: string) {
    this.email = email;
  }
}

index.tsx

const stores = {
  exampleStore
};

ReactDOM.render(
  <Provider {...stores}>
    <App />
  </Provider>,
  document.querySelector('#root')
);

App.tsx

interface Props {
  exampleStore?: ExampleStore;
}

@inject('exampleStore')
@observer
export class App extends React.Component<Props, {}> {
  componentDidMount() {
    setInterval(() => {
      this.props.exampleStore!.setEmail(Math.random() * 10 + '');
    }, 2500);
  }

  render() {
    const { email } = this.props.exampleStore!;
    return <div>{email}</div>;
  }
}

I have seen many examples use the useContext hook, but I have to use class components. I am not sure why this isn't calling the render function again. I have mobx and mobx-react installed.

Keimeno
  • 2,512
  • 1
  • 13
  • 34

3 Answers3

11

Are you using MobX 6?

Decorator API changed a little bit, now you need to use makeObservable method inside constructor to achieve same functionality as before:

class ExampleStore {
  @observable email = "hello";

  constructor() {
    makeObservable(this);
  }

  @action setEmail(email) {
    this.email = email;
  }
}

Althought there is new thing that will probably allow you to drop decorators altogether, makeAutoObservable:

class ExampleStore {
  email = "hello2";

  constructor() {
    // Don't need decorators now, just this call
    makeAutoObservable(this);
  }

  setEmail(email) {
    this.email = email;
  }
}

More info here: https://mobx.js.org/react-integration.html

Codesandbox: https://codesandbox.io/s/httpsstackoverflowcomquestions64268663-9fz6b?file=/src/App.js

Danila
  • 15,606
  • 2
  • 35
  • 67
0

Try withhout destructuring the email field:

  render() {
    return <div>{this.props.exampleStore!.email}</div>;
  }
Ivan V.
  • 7,593
  • 2
  • 36
  • 53
  • 1
    It would be the same thing, does not matter where exactly you dereference. I think he using mobx 6 and it has different api for decorators. – Danila Oct 08 '20 at 20:04
0

Like Danila mentions, you are likely hitting the change in MobX v6 where you have to explicitly make each class-instance observable, by calling makeObservable or makeAutoObservable in the class constructor:

class ExampleStore {
    constructor() {
        makeObservable(this);
    }
    @observable email = "hello";
    [...]
}

I'm not really a fan of the change though. It's not so much about the extra step of adding a constructor + function-call (for classes that otherwise don't need it); it has more to do with that it means I always have to "check the class" to make sure I've added the "activation call" for the field decorators I've added. In other words, it splits the "make this field observable" action into two parts, which sometimes are separated by substantial distances.

So anyway, my solution for this is to wrap the @observable decorator, and have it check the constructor's source-code to ensure that the call is being made: (performance impact is virtually nothing since it only runs when defining the class)

const observableWarningGivenFor = new WeakSet<Function>();
export const obs = ((target: Object, propertyKey: string | symbol)=>{
    if (target.constructor instanceof Function && !target.constructor.toString().includes("makeObservable")) {
        if (!observableWarningGivenFor.has(target.constructor)) {
            console.warn(`The @obs decorator was used on "`
                + target.constructor.name + "." + String(propertyKey)
                + `", but the class is missing the "makeObservable(this);" call.`
                + ` See here for more info: https://mobx.js.org/enabling-decorators.html`);
            observableWarningGivenFor.add(target.constructor);
        }
    }
    return observable(target, propertyKey);
}) as typeof observable;

// copy ".ref", etc. fields from "observable" (not wrapped)
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(observable))) {
    Object.defineProperty(obs, key, descriptor);
}

Usage: (same as normal)

class ExampleStore {
    constructor() {
        makeObservable(this);
    }
    @obs email = "hello";
    [...]
}

The only difference is that now, if I forget to add the makeObservable(this); call for a class that I added an @obs decorator for, I get a warning message.

Venryx
  • 15,624
  • 10
  • 70
  • 96