gaearon/react-hot-loader

RHL is recreating class arrow functions which breaks event listeners

Open

#1,273 opened on Jun 27, 2019

View on GitHub
 (10 comments) (0 reactions) (1 assignee)JavaScript (844 forks)batch import
discussionhelp wanted

Repository metrics

Stars
 (12,265 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

Let's say I have a component like this:

class A extends Component {
    componentDidMount() {
        window.addEventListener('resize', this.onResize)
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.onResize)
    }

    onResize = () => {
        this.setState({...})
    }

    ...
}

This is my babel 7 config:

{
    presets: [
      [
        "@babel/preset-env",
        {
          loose: true,
          modules: "commonjs",
          targets: {
            chrome: "66",
          }
        }
      ],
      "@babel/preset-react"
    ],
    plugins: [
      'react-hot-loader/babel',
      'add-module-exports',
      ["@babel/plugin-proposal-decorators", {legacy: true}],
      ["@babel/plugin-proposal-class-properties", {loose: true}],
    ]
  }

Babel with plugin-proposal-class-properties transpiles above component to roughly something like this:

class A extends Component {
    constructor() {
        super()
        this.onResize = () => {
            this.setState({...})
        }
    }

    componentDidMount() {
        window.addEventListener('resize', this.onResize)
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.onResize)
    }
}

When hot reloading happens, this.onResize is redefined and points to a new function, not the function that was previously registered as a resize listener in didMount. When component gets unmounted, removeEventListener obviously doesn't work because this new onResize listener has never been registered, and as a result I have memory leak, React complaining about calling setState on an unmounted component and all sorts of bugs.

For now, I just hacked the RHL's code to prevent updating arrow functions at all (src/proxy/inject.js):

        if (!isArrow && (
          nextString !== String(prevAttr) ||
          (injectedBefore && nextString !== String(injectedBefore))
        ))

Perhaps you will come up with a more proper solution.

Contributor guide