React-hot-loader: HotLoader won't work after adding >=2 levels of HoC.

Created on 3 Oct 2017  Â·  25Comments  Â·  Source: gaearon/react-hot-loader

Way to reproduce the issue:

import React, { Component } from 'react';

export function provideHoC() {
  return WrappedComponent => {
    class HoC extends Component {
      render() {
        return <WrappedComponent {...this.props} />
      }
    }

    HoC.displayName = (WrappedComponent.displayName || WrappedComponent.name) + 'WithHOC';
    return HoC;
  };
}
  • Modify src/Counter.js
 import React, { Component } from 'react';
+import { provideHoC } from './HoC';

-export default class Counter extends Component {
+class Counter extends Component {
   constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }

  componentDidMount() {
    this.interval = setInterval(this.tick.bind(this), 1000);
  }

  tick() {
    this.setState({ counter: this.state.counter + 1 });
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    return <h2>Counter: {this.state.counter}</h2>;
  }
}
+
+export default provideHoC()(provideHoC()(Counter));
  • npm start and go to http://localhost:3000.
  • Modify some text inside render method of Counter.
  • The counter state will be gone.
bug

Most helpful comment

Hard to fix on huge projects with tons of nested HoCs (since it is proper way to do HoCs). I created babel plugin to solve this:
https://www.npmjs.com/package/extract-hoc

All 25 comments

Due to internal logic, all spare parts (ie hot-replaceable) must be extracted as top-level variables, or HRL will not be able to hot-swap them.
This is a stopper for HoC or decorators. It is just easier to convert HOCs to a normal, prop-based components, and use react, not functional composition, to create target logic.
Or just use

 const level1 = provideHoC()(Counter);
 const level2 = provideHoC()(level1);
 export default level2;

We have to add this to documentation.

@neoziro. Never too late. I'll do it.

@theKashey done in #655

Hard to fix on huge projects with tons of nested HoCs (since it is proper way to do HoCs). I created babel plugin to solve this:
https://www.npmjs.com/package/extract-hoc

@quangbuule - looking quite good, but, please, add tests and a bit more description.

This is good, we could eventually integrate it in the project if it is enough stable.

Still running into this issue, even after using extra-hoc babel plugin and manually extracting HoCs into separate components

Package.json: https://github.com/blazing-edge-labs/admin-playground/blob/master/package.json
Component with HoCs: https://github.com/blazing-edge-labs/admin-playground/blob/master/src/modules/Examples/Profile/Edit/index.js

I'm getting the error with this plugin as well.
Also I realised that HOCs cause component state reload now.
@quangbuule could you confirm this part of bug as well? Does your plugin assume to fix state reload bug?
Thank you.

Here are the errors I get:

React Hot Loader: this component is not accepted by Hot Loader. 
Please check is it extracted as a top level class, a function or a variable. 
Click below to reveal the source location: 
 Æ’ Connect(props, context) {
        _classCallCheck(this, Connect);

        var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));

        _this.version = version;
     …
React Hot Loader: this component is not accepted by Hot Loader. 
Please check is it extracted as a top level class, a function or a variable. 
Click below to reveal the source location: 
 Æ’ Form() {
          var _ref;

          var _temp, _this, _ret;

          _classCallCheck(this, Form);

          for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++)…

@mqklin No, the react-hot-loader is suppose to be better than previous version/library, but from what I see, developing app with react-transform-hmr is much less painful. There are several cases that your code might have and won't work with react-hot-loader. My library is just to solve the case when you used nested HoC wrappers.

It basically convert this (which not work with react-hot-loader):

var FinalComponent = foo()(bar()(TheComponent))

to things like this:

var FinalComponent_arg1 = bar()(TheComponent);
var FinalComponent = foo()(FinalComponent_arg1);

@mateoKaradza - just have a look on your project.
First - you did not wrap your App with HotContainer. Next - there is no errors in console. But state is dropping on update :(
According to your message - you have problems with the internals of redux-form.

@theKashey thanks for taking a look
Here is the HotContainer for the app: https://github.com/blazing-edge-labs/admin-playground/blob/master/src/index.js#L12

There are no errors in the console because I downgraded version of React Hot Loader, latest version was broken for me, I'd get a message in console saying that the component was updated, but it didn't affect loaded app in the browser (any component). I switched to an older version and now it works fine.

Issue with redux-form is with its initialValues prop. After hot reload, the forms gets re-rendered and it doesn't reload initial values. I saw latests PRs and there was a fix where such functionality can be disabled in development mode, looking forward to test it once they release next version.

@mateoKaradza are you expecting redux form to reset values to the initial state on remount? There must be no remount.
Will try to dig your case once again.

PS:

Here is the HotContainer for the app: https://github.com/blazing-edge-labs/admin-playground/blob/master/src/index.js#L12

I still can't see it. You just accept changes.
You have to wrap whole application with AppContainer

import { AppContainer } from 'react-hot-loader'; //   <------
const render = Component => {
  ReactDOM.render(
    <AppContainer> <<--- not exists in your code.
      <Component />
    </AppContainer>,
    document.getElementById('root'),
  )
}

Without this component magic will not work.

@quangbuule doest react-transform-hmr work with React16 and decorators?

@mqklin have no idea, my team haven't used the React 16 yet :).

@mqklin I don't think react-transform-hmr works with React 16, but I can't confirm.

@neoziro so, as I understand, I can't use RHL3 with decorators, right? Because state is reset on every hot reload?

@mqklin - as long there is no "decorators" in JS you can try to add babel plugins

  1. babel-plugin-transform-decorators-legacy. Which will convert decorator to a HOC.
  2. extract-hoc, which will transform that HOC to the digestible by RHL code.
    The order is crucial.

ie

"plugins": [
    "transform-decorators-legacy",
    "extract-hoc/babel",
    "react-hot-loader/babel"
  ]

PS: Might not work. Having no chance to properly test it, sorry.

https://github.com/quangbuule/extract-hoc solved this issue for me for react-apollo and react-redux HOCs. Would be great if this was integrated somehow.

@theKashey @transcranial still React Hot Loader: this component is not accepted by Hot Loader.
Here is a repro project:
https://github.com/mqklin/rhl3-decorators-bug
And a gif:
rhl
You can see that state breaks only if I use decorators (I use extract-hoc, doesn't help)

I just tried it with react-transform-hmr and got exactly the same problem (works only without decorators). Here is PR: https://github.com/mqklin/rhl3-decorators-bug/pull/1/files

Decorators will not work at all. There is __no way__ to make them work.
Except #711 - it will literally solve everything. I'll do my best to make v4 sorted out before Christmas.

A long issue finally fixed in React Hot Loader v4.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mtscout6 picture mtscout6  Â·  3Comments

adesmet picture adesmet  Â·  4Comments

mattkrick picture mattkrick  Â·  3Comments

theKashey picture theKashey  Â·  4Comments

jljorgenson18 picture jljorgenson18  Â·  3Comments