React-hot-loader: Strange behavior - class methods seem to be hijacked / overwritten by RHL?

Created on 23 Oct 2017  路  16Comments  路  Source: gaearon/react-hot-loader

Description

I have a class like this:

export default class ApiClient {
  constructor(req) {
    this.get = function () { console.log("get"); }
    console.log('constructed ', this.get);
  }
}

When this class is executed, here's what it logs:

constructed  function () {
       return _this.__get__REACT_HOT_LOADER__.apply(_this, arguments);
     }

More specifically, I'm doing this in a loop in the constructor, and it gets stranger:

export default class ApiClient {
  constructor(req) {
    [ 'get', 'put' ].forEach(method => {
      this[method] = () => { console.log(method); };
    });
    console.log('constructed ', this.get);
    console.log('constructed ', this.put);
  }
}

It logs nothing:

constructed  undefined
constructed  undefined

And when I log the client itself (since downstream code expects client.get to exist):

created new client ApiClient { method: [Function] } undefined undefined

What is going on here? I'm not sure how to debug this, and why my constructor loop isn't running, and why this library is hijacking it.

This is executed in a node server context:

app.use((req, res) => {
  if (__DEVELOPMENT__) {
    webpackIsomorphicTools.refresh();
  }
  const client = new ApiClient(req);

This code path is also executed by babel on the fly on the server, using require('babel-register')

My .babelrc:

{
  "presets": ["react", "es2015", "stage-0"],

  "plugins": [
    "transform-runtime",
    "add-module-exports",
    "react-hot-loader/babel",
    "transform-decorators-legacy",
    "transform-react-display-name"
  ]
}

Environment

React Hot Loader version: 3.1.1

Run these commands in the project folder and fill in their results:

  1. node -v: 8.6.0
  2. npm -v: 5.5.1
bug

Most helpful comment

@fwh1990 - currently you have to transpile arrow function. To be fixed in next release.

All 16 comments

RHL contain a special logic to "hack" arrow function and inject another context("this") into an arrow function.
That could lead to some issues :(

How to solve: Find a way to protect a file from RHL's babel plugin.

Not easy doable. First, that comes to my mind - use webpack with 2 babel's profiles. One for jsx(or /components) and RHL inside, and one for the rest of files.

@theKashey We need to find a better way to make it work... It is too hacky today. It should not impact code that is not relative to React. Maybe the Babel strategy is not the good one. I will try to take back and think about another solution to do it.

@neoziro - I've found a better solution, will provide some code in an hour or two. Everything is quite simple, but better to double check.

@theKashey 馃

Should this be reflected in the readme, that the fundamental strategy conflicts with non-react code? Not trying to be an ass, but since dan is pointing people to this library, the readme doesn't give any indication about that status (I don't know if "experimental" is the right word?). It took me by surprise

@AndrewRayCode it is stable but it still has some bugs, must of them are due to the approach (Babel is dangerous). We will try to reduce Babel scope in the next version.

Following code:

['foo', 'bar', 'barfoo'].map((method) => {
  this[method] = ...
})

was transformed to

['foo', 'bar', 'barfoo'].map((method) => {
  this.method = ...
})

Hi @neoziro,
is there a way to bypass RHL patch when using typescript and targeting ES6?

Thanks!

@harshes53 - the problem IS with ES6. But you can try to use webpack loader instead of babel plugin. It does not have any magics inside (and that's the problem of loader solution :) )

@theKashey thanks!

FYI I don't have any babel usage in my project.. so trying to figure out how to use RHL with TS and ES6 target.

Heh, good luck.
I am not sure that webpack loader could handle ES6 code (arrow functions). Everything has some limitations.
I'll propose to add babel-loader to your dev webpack configuration, and babel all the things into ES5, at least in dev mode.
ES6 code does not always work due some magic code transforms we are doing, sorry.

Using setState() in callback method will cause a warning by react,
(Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component.)

Just happened when my code like : export default connect(mapState2Props)(xxxComponent). And I bind my callbackFunction in constructor already(this.handleCallback = this.handleCallback.bind(this);).

But, if I write my code by decorator :

@connect()
export class xxxComponent extend React.Component {}

The warning upon will be gone. However, component's lifecycle will refresh when I change my code, it means that each component will trigger componentWillMount again.

How to fix?

And now , I have to disable rhl, sadly. By the way, I am using react 16.2, and only support chrome 56+, it means, I didn't transform es6 to 5.

@fwh1990 - currently you have to transpile arrow function. To be fixed in next release.

Arrow function are no longer transpiled in React Hot Loader v4.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mqklin picture mqklin  路  3Comments

rockchalkwushock picture rockchalkwushock  路  3Comments

mtscout6 picture mtscout6  路  3Comments

sandysaders picture sandysaders  路  4Comments

Opty1712 picture Opty1712  路  4Comments