Search code examples
reactjswebpackreact-hot-loader

"react-hot-loader/babel" breaks my build -- why is it needed?


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:

  1. What does "react-hot-loader/babel" actually do?
  2. How do I get it to not break my class properties?

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"
                    }
                ]
            ]
        }
    }
}

Solution

  • 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.