As soon as I add "react-hot-loader/babel"
to my .babelrc
, it breaks my React components.
Specifically, I have some code that looks like this:
export default class Editor extends React.Component {
componentDidMount() {
console.log('this.canvas',this.canvas);
// ...
}
setRef = node => {
console.log('setRef');
this.canvas = node;
}
render() {
// tabIndex is needed to receive keyboard events -- https://stackoverflow.com/a/12887221/65387
return <canvas className={this.props.className} ref={this.setRef} tabIndex={0} />;
}
}
When I run it, I see this in my Chrome dev tools:
setRef
this.canvas undefined
Which is quite strange, because we can see it's setting this.canvas
before calling componentDidMount
so I don't know what react-hot-loader/babel
is doing to break that.
Without react-hot-loader/babel
, everything works fine, including hot-reloading.
So, my questions are:
This is with React 16.1, react-hot-loader 3, webpack 3.11, babel 6.x
My .babelrc
if you want to see that:
{
"plugins": [
"transform-object-rest-spread",
"syntax-jsx",
"transform-react-jsx",
"transform-react-display-name",
"transform-class-properties",
"transform-function-bind",
"transform-decorators-legacy"
],
"compact": false,
"env": {
"development": {
"plugins": [
"transform-react-jsx-self",
"transform-react-jsx-source",
[
"styled-components",
{
"displayName": true,
"minify": false
}
]
// https://stackoverflow.com/q/48857689/65387
//"react-hot-loader/babel"
],
"presets": [
[
"env",
{
"targets": {
"browsers": "last 2 chrome versions"
},
"modules": false
}
]
],
},
"webpack": {
"presets": [
[
"env",
{
"targets": {
"node": "current"
},
"modules": "commonjs"
}
]
]
}
}
}
This seems to be a bug in react-hot-loader v3 (I was able to reproduce the issue) and it is fixed in react-hot-loader v4.
As per this comment, this issue seems to be caused by the proxying logic in react-proxy. One trick is to store an object to hold your references, this will get copied to the proxy by react-proxy and will be available in the proxy version of this
:
export default class App extends React.Component {
constructor(props) {
super(props);
this.myRefs = {};
this.setRef = this.setRef.bind(this);
}
componentDidMount() {
console.log('componentDidMount',this.myRefs.div); // <-- Not null!
}
setRef(node) {
this.myRefs.div = node;
console.log('setRef',this.myRefs.div);
};
render() {
return <div ref={this.setRef}>test</div>
}
}
Or as mentioned in the next comment, make sure your function bindings are done in componentWillMount
:
export default class App extends React.Component {
constructor(props) {
super(props);
// Do not bind here, "this" will get proxied.
}
// Proxy "this" is now available. Bind here.
componentWillMount() {
this.setRef = this.setRef.bind(this)
}
componentDidMount() {
console.log('componentDidMount',this.div); // <-- Not null!
}
setRef(node) {
this.div = node;
console.log('setRef',this.div);
};
render() {
return <div ref={this.setRef}>test</div>
}
}
I did verify the code in question with react-hot-loader v4 and it is fixed there.