'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
- clone
- bundle
- rails s
- (in another process) ./bin/webpack-dev-server --host 127.0.0.1
- 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 |