12

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'),
  )
})
Ayushya
  • 9,599
  • 6
  • 41
  • 57
wiznaibus
  • 661
  • 3
  • 8
  • 17
  • [**It works for me**](http://jsbin.com/xosusiduci/edit?js,output). Can you work out what differs in you case? – Tholle Oct 01 '17 at 14:04
  • No luck. I copied your code directly into my editor. @Tholle. – wiznaibus Oct 01 '17 at 14:13
  • That's annoying. The only thing I see that looks a little bit suspicious is the `import UserStore from '../bundles/User/stores/UserStore';`. Is `../bundles/User/stores/UserStore` the intended import? – Tholle Oct 01 '17 at 14:21
  • I thought that after I saw your code. But after I literally copy/pasted your code into one big file, it still runs as previously described. I'm currently loading up a brand new app to check there. – wiznaibus Oct 01 '17 at 14:22
  • Added new rails/react app in the question. Your code is copied directly into `app/javascript/packs/hello_react.jsx` file, @tholle. Same problem unfortunately. – wiznaibus Oct 01 '17 at 14:37
  • 1
    Bummer. Could you try to put `transform-decorators-legacy` first in the list of babel plugins? – Tholle Oct 01 '17 at 15:24
  • 3
    I went mad over this problem. I was hunched over my laptop since 4PM yesterday, barely slept, drank way too much coffee, snapped at my wife, and finally...I have a resolution. Thank you @Tholle! – wiznaibus Oct 01 '17 at 15:28

8 Answers8

12

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.

Alin Ciocan
  • 3,082
  • 4
  • 34
  • 47
  • 1
    You save my life – user3896501 Oct 31 '19 at 11:56
  • @ArjunTRaj maybe it's something else that is messing with Mobx. Try to only use inject and observer on the component and see if that's the issue. Otherwise, it might be something different. I have discovered that unless you read from the store in the component, the render it's not triggered. – Alin Ciocan Nov 26 '19 at 11:22
9

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

makeObservable(this)
tarik203
  • 375
  • 3
  • 9
  • Thanks. it works for me. But you have a typo. It should be `makeObservable(this)` – hakki Dec 07 '20 at 14:59
  • I am so happy that I found this answer, thank you! – Piotr Sobuś Feb 14 '21 at 21:15
  • did anyone else experience a bug where the store is observed in local environment but not in production? Solved this by attaching an observer to child components, but I'm not sure if this was designed to work this way. – mymoto Apr 14 '21 at 04:53
  • This worked! However, why does the `@observer` decorator no longer work? It doesn't even give a warning... (versions: mobx@6.3.2, mobx-react@7.2.0 -- I noticed the `@observer` decorator was no longer working after updating from mobx 5 to 6 today) – Venryx Jul 24 '21 at 01:39
  • Okay, I found the answer: https://mobx.js.org/enabling-decorators.html The problem was not the `@observer` class-decorator, it was the `@observable` _field_ decorator -- the field-decorators now require a call within the constructor to "be applied". (see link above for details) – Venryx Jul 24 '21 at 02:27
6

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.

Tholle
  • 108,070
  • 19
  • 198
  • 189
  • 1
    I ran into this issue recently while using parcel-bundler. This is due to what appears to be a bug in parcel not properly parsing tsconfig.json files. See [here](https://github.com/parcel-bundler/parcel/issues/2129#issuecomment-459572127). – Adam Mazzarella Feb 01 '19 at 04:19
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

jonyB
  • 9,257
  • 2
  • 8
  • 11
  • 2
    You are not alone man, I also forgot to wrap my functional component with observer function. 10K+ reputation... mean nothing :) – shift66 Feb 18 '21 at 07:17
3
<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.

Daniel
  • 1,933
  • 12
  • 17
1

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.

Tom Fennelly
  • 286
  • 1
  • 2
  • 7
0

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.

Ivan
  • 130
  • 8
0

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

Boude
  • 677
  • 6
  • 13