'React not rerendering after mobx observer change

Upon page load, I see "hi2" When I click the button, nothing happens. I tried with setUser as well.

I suspect I'm just editing the props themselves and somehow the observable is not being triggered?

See sample code of it not working here in a brand new rails/react environment: https://github.com/bufordtaylor/mobxtest

  1. clone
  2. bundle
  3. rails s
  4. (in another process) ./bin/webpack-dev-server --host 127.0.0.1
  5. navigate to localhost:3000

======================

UPDATE:

I've reduced it to it's basic form, eliminating possible import errors, Provider errors, or constructor errors.

Here it is

import React from 'react'
import ReactDOM from 'react-dom'
import { observable, action, computed } from 'mobx';
import { Provider, inject, observer } from 'mobx-react';


class UserStore {

  @action setUser(val) {
    console.log(val);
    this.user = val;
  }

  @observable user = "default";
}

const userStore = new UserStore();

@observer
class Hello extends React.Component {
  render() {
    return (
      <div>
        hi2 {this.props.userStore.user}
        <button onClick={this.props.userStore.setUser.bind(this,"fwefwe")}>faew</button>
      </div>
    )
  }
}

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Hello userStore={userStore} />,
    document.getElementById('app'),
  )
})


Solution 1:[1]

Your code looks sound. I think you have stumbled upon an issue discussed in the How to (not) use decorators part of the documentation. It is important that transform-decorators-legacy is first in the list of babel plugins.

Solution 2:[2]

My problem was the order of wrapping the component, because I was using Material UI framework.

Wrong:

export inject('store')(observer(withStyles(styles)(MyComponent)));

Correct:

export withStyles(styles)(inject('store')(observer(MyComponent)));

So, it's important the order with MobX and React Material UI.

Solution 3:[3]

if you use the latest version of mobx, and babel version 7.12 add this to you constructor

makeObservable(this)

Solution 4:[4]

For anyone arriving here, make sure you're not being a complete idiot like me and that you didn't forget to add the @observer decorator before the class component.

(Or the @observable decorator in the store)

God, wasted a full day on that

Solution 5:[5]

<button onClick={this.props.userStore.setUser.bind(this,"fwefwe")}>faew</button>

Be careful. You are binding this. This in this case is the instance of the Hello Component. Now the this in the setUser function points to the Hello Component. So setUser will set a property user in the Hello Component.

 @action setUser(val) {
    console.log(val);
    this.user = val; // This this now points to the Hello Component.
  }

To understand what I mean, you can set a breakpoint on the setUser method, then inspect the variable this. You will see that it points to the Hello Component and not to your stores instance.

Instead do the following:

<button onClick={() => { this.props.userStore.setUser("fwefwe"); }}>faew</button>

Here I am creating a lambda that calls setUser on the user store. Because I am using a lambda here, the this in this.props.userStore points to the Hello Component.

Solution 6:[6]

I had similar issue, I was using arrow function for render method:

render = (): React.ReactNode

correct should be:

render(): React.ReactNode

still not sure why first case confuses mobx.

Solution 7:[7]

I'm sure there are multiple reasons why this can happen.

In my case I was NOT using decorators. I was just using makeAutoObservable(this) in the constructor of my mobx state object. The reason why my component was not re-rendering on state change was because I didn't apply a default value to the state property.

I had a property defined like this:

showModalForIssue: IssueTreeNode;

I had an observer component that was using this property but was not re-rendering on a change to it's value.

After much dicking about I eventually fixed it by simply applying a default value (setting it to null) in it's definition as follows:

showModalForIssue: IssueTreeNode = null;

It must be something to do with how makeAutoObservable(this) works.

Solution 8:[8]

In my case, whilst running an old version of mobx (4.3), the issue came from a component which rendered another component and passed a lambda to that component which returns a node

@observer
class Parent extends Component {
  @observable
  value = "Initial";

  renderSub() {
    return <Text>{this.value}</Text>
  }

  render() {
    return <Child primary={() => this.renderSub()} onPress={() => (this.value = "Other")} />;
  }
}

class Child extens Component {
  render() {
    return this.props.primary();
  }
}

This would break. The fix was to instead pass the node directly:

class Parent extends Component {
  render() {
    return <Child primary={this.renderSub()} />;
  }
}

This is probably because of the rendering happening in a different component because of the lambda so that falls outside of the change tracking of the Parent component, another possible fix might have been to make the Child component an @observer as well, but I prefer my fix, so I didn't try that

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tholle
Solution 2
Solution 3
Solution 4 jonyB
Solution 5
Solution 6 Ivan
Solution 7 Tom Fennelly
Solution 8 Boude