Skip to content
This repository has been archived by the owner on Dec 31, 2020. It is now read-only.

Hot reload doesn't work when @observer is used #500

Closed
eyousefifar opened this issue Jun 9, 2018 · 22 comments
Closed

Hot reload doesn't work when @observer is used #500

eyousefifar opened this issue Jun 9, 2018 · 22 comments

Comments

@eyousefifar
Copy link

eyousefifar commented Jun 9, 2018

package version:
"mobx": "^4.3.1",
"mobx-react": "^5.2.2",
"react": "16.3.1",
"react-native": "0.55.4"
platform: Android
Hi, when I add @ observer to my react class, hot reloading doesn't work. it reloads the code but UI doesn't rerender. it works fine without an observer
I can provide more information if needed

@urugator
Copy link
Contributor

urugator commented Jun 9, 2018

@eyousefifar
Copy link
Author

@urugator tnx, but I used class components and removed decorators, and used observer like this :
export default observer(App) where App is my components class, but as soon as I put observer to work, HMR stops working.

@luco
Copy link

luco commented Jun 11, 2018

+1 on this.
Using export default observer(MyClassName) solved the problem. Thanks!

@briedel1
Copy link

briedel1 commented Jul 22, 2018

cc @theKashey

Hi - I'm seeing a similar issue, not resolved by the above suggestions. The mobx dependency graph works correctly, but HMR does not re-render when changing UI.

In the example below, both <App/> and <Comp/> are observer components. HMR does not re-render UI updates when observer(<Comp/>) depends on this.state.obsObj (try toggling 1px border to 10px border).

If you switch the observer(<Comp/> dependency from this.state.obsObj -> this.props.obsObj, HMR does re-render the UI update.

Alternatively, if I remove the observer HOC from the <App/> component and keep the observer(<Comp/>) dependency on this.state.obsObj, HMR does re-render the UI update.

Any thoughts? Thanks v much.

import React, {Component} from 'react';
import {hot} from 'react-hot-loader';
import {observer} from 'mobx-react';
import {observable} from 'mobx';

class Comp extends Component {
    state = {
        obsObj: null
    };

    static getDerivedStateFromProps(nextProps, prevState) {
        return {obsObj: nextProps.obsObj};
    }

    render() {
        const {prop1} = this.state.obsObj;
        return <div style={{border: '1px solid red'}}>{prop1}</div>;
    }
}

const ObsComp = observer(Comp);

class App extends Component {
    @observable obj = {
        prop1: 'Example'
    };

    render() {
        return (
            <div>
                <ObsComp obsObj={this.obj}></ObsComp>
            </div>
        );
    }
}

const ObsApp = observer(App);

export default hot(module)(ObsApp);

Dependencies:
"mobx": "^5.0.3",
"mobx-react": "^5.2.3",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-hot-loader": "^4.3.3"

@theKashey
Copy link
Contributor

@briedel1 - you just spotted one of RHL's limitations. I could understand why it will not work for SFC wrapper by observable (it will not, due to componentClass is trapped in function scope), but I don't have explanation why it does not work for classes.

Let me play with it for a while.

@theKashey
Copy link
Contributor

So - there are always just 2 issues:

  • something don't get updated. HMR\RHL could not update some things. Re-rendering will not "fix" it.
  • something don't get re-rendered. RHL tries to force-update everything, but that doesn't always work. So - may be HMR/RHL did their job here, just MobX not seeing reason to update component, or visa versa. If it will be re-rendered by the App's property change - that's the case.

Anyway - don't touch anything :), I need a broken example to fix.

@briedel1
Copy link

Thanks for taking a look.

@theKashey
Copy link
Contributor

theKashey commented Jul 25, 2018

So everything is badly broken.
You may enable RHL debug mode using setConfig, and then see the problem

bundle.js:918 React-hot-loader: reconciliation failed due to error TypeError: Cannot read property 'obsObj' of undefined

this.state - is really undefined, due to Symbol('state value holder') created in makeObservableProp is unique. So on reload it will be override existing property, but will be without value for a while, as long old "state" has been store in another Symbol.

I've tried to "concrete" them, so allocate one symbol per properly name, and it fixes the problem.

@mweststrate - is there any sense to use unique symbols to shadow "well-known" props like state and props?

@mweststrate
Copy link
Member

mweststrate commented Jul 26, 2018

@theKashey the symbols only need to be unique for the mobx-react module, so as long as the module isn't reloaded, the symbols could stay the same. So I think the symbol created here: https://github.com/mobxjs/mobx-react/blob/master/src/observer.js#L278 could be also build as const valueHolderKey = ${someGloballyDefinedSymbol} ${propName} holder` to fix this issue

@theKashey
Copy link
Contributor

What about just caching them? It createSymbol could return the same value for the same keys?
If ok - I'll double check from my side and draft a PR.

@mweststrate
Copy link
Member

mweststrate commented Jul 26, 2018 via email

theKashey added a commit to theKashey/mobx-react that referenced this issue Jul 28, 2018
@briedel1
Copy link

briedel1 commented Aug 3, 2018

Thanks for the help and quick turnaround @theKashey !

@mweststrate
Copy link
Member

Released as 5.2.5. Thanks for investigating and fixing @theKashey !

@reza7rm
Copy link

reza7rm commented Oct 24, 2018

Released as 5.2.5. Thanks for investigating and fixing @theKashey !
Hi
I still have the same problem with the following versions:
"mobx": "^4.5.0",
"mobx-react": "^5.3.3",
"mobx-state-tree": "^3.7.0",

@Kruemelkatze
Copy link

Kruemelkatze commented Nov 6, 2018

Released as 5.2.5. Thanks for investigating and fixing @theKashey !
Hi
I still have the same problem with the following versions:
"mobx": "^4.5.0",
"mobx-react": "^5.3.3",
"mobx-state-tree": "^3.7.0",

@reza7rm
I had the same issue, but downgrading mobx-react to 5.2.8 seems to work. <5.2.5 and >5.2.8 wouldn't work.

Our Dependencies:

"mobx": "4.5.2", // Thanks to IE
"mobx-react": "5.2.8",
"react": "^16.6.0",
"react-dom": "^16.6.0",
"react-hot-loader": "~4.3.12",

With this setup, I got a nested component (with state, an injected store and @ observer) to update both on store changes (via mobx) and file changes like changing text (via HMR). Our test component:

import React, { Component } from 'react'
import { observer, inject } from 'mobx-react';

@inject("permissionStore")
@observer
class Dummy extends Component {

    state = {
        switchedIcon: false,
    }

    render() {
        let userName = `${this.props.permissionStore.user.givenName} ${this.props.permissionStore.user.surName}`;
        return (
            <div>
                <h2>{userName}</h2>

                <button onClick={() => this.setState({ switchedIcon: !this.state.switchedIcon })}>Switch Icon</button>
                <FontAwesomeIcon icon={this.state.switchedIcon ? 'minus' : 'plus'} />
            </div>
        )
    }
}

export default Dummy

Our App root component is also decorated with @ observer.

@observer
class App extends Component {
    render() {
        return (
            <Provider permissionStore={PermissionStore}>
                <BrowserRouter>
                   <Routes /> // The Dummy component is rendered somewhere in here
                </BrowserRouter>
            </Provider>
        );
    }
}

This setup did not work with <5.2.5 and none of the ~5.3.x releases of mobx-react. But as I said, 5.2.8 works fine.

@zfalen
Copy link

zfalen commented Nov 14, 2018

Strange thing I noticed over here, experiencing the exact same behavior. MobX observer tag is preventing child components from hot reloading.

However - I had the same setup (except using webpack-hot-middleware) working on a totally different project, and started looking for other differences in config....

It turns out that removing the import "babel-polyfill"; line totally fixed the issue. I was using that import to enable async / await but turning it off completely re-enabled HMR. Not sure what the implications are, if any, but maybe a useful clue?
@theKashey @mweststrate

Note: HMR and observer are confirmed working at the root and child level, as expected. Local state is preserved during HMR reload. I did not need to downgrade libraries to fix.

Dependencies:
"mobx": "4.5.2", "mobx-react": "5.2.8", "react": "^16.6.0", "react-dom": "^16.6.0", "react-hot-loader": "~4.3.12",

Top-level react component (with react-hot-loader setup):
class App extends Component { ... } export default hot(module)(App);

Child react component:
@observer export default class SomeComponent extends Component { ... }

@theKashey
Copy link
Contributor

@zfalen babel-polyfill should be the first import in a whole project. Was it?
Having it imported not in the first place could lead to undefined behaviour.

Easiest example

import 'react'
import 'babel-polyfill'
import 'react-dom';
🔥 react and react-dom got different "Symbol" in IE11, everything is broken

PS: please ensure that you have modules:false in your babel config, or react-hot-loader will import itself(and react) in a first place, probably breaking some stuff.

@zfalen
Copy link

zfalen commented Nov 14, 2018

@theKashey TLDR I found the issue - dont ever use an arrow function on lifecyle methods (or any other format for that matter. Even if there was a fire)

Here are some longwinded tweaking results that may or may not help you or other people experiencing the issue :)

babel-polyfill is the first line in the index file:

import "babel-polyfill";
import "../sass/index.scss";
import React from "react";
import ReactDOM from "react-dom";

// hot(module)(App) is in this file 
import App from "./components/app";

ReactDOM.render(<App />, document.getElementById("app"));

I also tried adding modules: false directly in the webpack config (though it didnt seem to make any difference in behavior):

  {
      test: /\.(jsx)$/,
      exclude: /(node_modules|bower_components)/,
      use: [
        {
          loader: "babel-loader?cacheDirectory",
          options: {
            cacheDirectory: true,
            presets: [
              ["@babel/preset-env", { modules: false }],
              "@babel/react",
              "mobx"
            ],
            plugins: ["react-hot-loader/babel"]
          }
        }
      ]
    },

However, I either totally misreported what I was experiencing at first, or I am on drugs. Testing this morning things started breaking again, which led me down another debug path. As it turns out, babel-polyfill didn't affect HMR today. Instead, what I noticed was that I had a lifecycle method declaration in the child component which was created as an arrow function for some unknown reason I cannot explain. Eg:

componentDidMount = () => {
  fetch("http://0.0.0.0:3333/")
    .then(res => res.json())
    .then(json => (this.serverData = json));
}

This did not work. Commenting out that line while trying to get things to work again did get HMR working. Voilé! A few more tweaks / experiments later, and I notice:

componentDidMount = function() { ... }
Did not work either.

componentDidMount() { ... }
Does work as expected! As one should hope, since this is the right way to do that.

Some more variations and their results:

@action
componentDidMount() {
  fetch("http://0.0.0.0:3333/")
    .then(res => res.json())
    .then(json => (this.serverData = json));
}

Works.

async componentDidMount() {
   const res = await fetch("http://0.0.0.0:3333/");
   const json = await res.json();
   this.serverData = json;
 }

Works. (w/polyfill)

@action updateServerData(val) {
   this.serverData = val;
 }

 async componentDidMount() {
   ...
   this.updateServerData(json);
 }

Works. (w/polyfill)

Awesome. Seem to have found the issue. Doing weird and unnecessary things to standard React lifecycle methods prevent the component from hot reloading. It was not an issue with MobX observer. Notably, it happens with render too:

render = () => (
  <div>
    <h4>I Am A SubComponent</h4>
    ...
  </div>
);

Does Not Work.

Although stateless components written with an arrow function do work as expected with HMR:

const Stateless = ({ someProp }) => <h2>{someProp}</h2>;
...
<Stateless someProp={'hello'} />

@theKashey
Copy link
Contributor

We had an issue with render-as-arrow-function, but, yeah, we never tested componentDidMount-as-arrow-function 🙀

@wuifdesign
Copy link

wuifdesign commented Jan 23, 2019

I'm still having problems with observer decorator in React Native.

"mobx": "^5.9.0",
"mobx-react": "^5.4.3",
"react": "16.6.3",
"react-native": "0.57.8"

React Native HMR not working when using a decorator like this:

import React from 'react';
import {Text, View} from 'react-native';
import {observer} from 'mobx-react/native'

@observer
export default class App extends Component {
  render() {
    return (
      <View>
        <Text>Demo Text</Text>
      </View>
    );
  }
}

but this works when calling as a funtion:

import React from 'react';
import {Text, View} from 'react-native';
import {observer} from 'mobx-react/native'

class App extends Component {
  render() {
    return (
      <View>
        <Text>Demo Text</Text>
      </View>
    );
  }
}

export default observer(App);

it also works when using observer decorator with inject decorator like this:

import React from 'react';
import {Text, View} from 'react-native';
import {observer} from 'mobx-react/native'

@inject()
@observer
export default class App extends Component {
  render() {
    return (
      <View>
        <Text>Demo Text</Text>
      </View>
    );
  }
}

@theKashey
Copy link
Contributor

@wuifdesign - react-native uses quite old version of RHL, and there is no way for me to help you here.

@jrmyio
Copy link

jrmyio commented Apr 26, 2019

@theKashey @mweststrate @eyousefifar Can anyone re-open this issue?
You still need to explicitly use "mobx-react": "5.2.8" in order for the issue to not occur.

Apparently the fix implemented in 5.2.5 broke again in 5.3 >.

Just realized there is also this: #559

If it's easier, it would also be helpful if things starting to work again with 6.*.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests