/ react

Binding functions in React components the right way

The problem

If you have worked with React even for just a little bit, you have most definitely encountered the Cannot find X of undefined error, which is a result of trying to do this:

class Comp extends Component {
    constructor() {
        super()
        this.state = {
            msg: '',
        }
    }
    handleInput() {
        this.setState({ msg: e.target.value })
    }
    render() {
        return <input type="text" onChange={this.handleInput} value={this.state.msg} />
    }
}

This happens because custom functions such as handleInput lose their this binding. If you attempt to access this.state, this.props or this.setState within them will result in the common error, and is solved by re-binding the functions in the constructor:

class Comp extends Component {
    constructor() {
        super()
        this.state = {
            msg: '',
        }
        this.handleInput = this.handleInput.bind(this)
    }
    handleInput() {
        this.setState({ msg: e.target.value })
    }
    render() {
        return <input type="text" onChange={this.handleInput} value={this.state.msg} />
    }
}

You might think that this gets tedious after binding a couple of functions and want to find another way, so you might encounter this:

class Comp extends Component {
    constructor() {
        super()
        this.state = {
            msg: '',
        }
    }
    handleInput() {
        this.setState({ msg: e.target.value })
    }
    render() {
        return <input type="text" onChange={this.handleInput.bind(this)} value={this.state.msg} />
    }
}

Here you can bind this when the function is passed as a prop. While it might be tempting to do this all over your application, this method has a huge downside. What it does is that it will create a new function every time that the render method executes, which for larger applications will be catastrophic while it might not be as noticable in smaller applications. For more information on this, you can read this case study.

"But wait, what about arrow functions?" - yes, you are right, you could also remove this function and do setState right there when passing the onChange prop.

class Comp extends Component {
    constructor() {
        super()
        this.state = {
            msg: '',
        }
    }
    render() {
        return <input type="text" onChange={(event) => this.setState({ msg: event.target.value })} value={this.state.msg} />
    }
}

While it might be easier to see what is going on here, it suffers from the same problem as the previous example. Now we are creating an anonymous function every time the render function executes, with the same performance issues as well.

Solving the problem

This means that the best way is indeed to bind the functions in the constructor if you want to avoid the performance issues. Then only one extra function is created when the component is created and that function will be used even if render executes again, without creating new ones every time.

"So that's it, I'm stuck with re-binding ALL my functions every time?!" - Thankfully, no. Here we can actually have the best of both worlds thanks to babel, which you are most likely already using to transpile your code. (If not, you probably should!). Babel uses presets and plugins and there are a ton of them. We are interested in a particular one, called transform-class-properties.

Install it with your package manager of choice:

> npm install babel-plugin-transform-class-properties

Then modify your .babelrc (taken from README):

// without options
{
  "plugins": ["transform-class-properties"]
}

// with options
{
  "plugins": [
    ["transform-class-properties", { "spec": true }]
  ]
}

Now, you can write automatically bound functions using the arrow function syntax, let's take a look at the final result:

class Comp extends Component {
    constructor() {
        super()
        this.state = {
            msg: '',
        }
    }
    handleInput = event => this.setState({ msg: event.target.value })
    render() {
        return <input type="text" onChange={this.handleInput} value={this.state.msg} />
    }
}

Conclusion

We encountered a bothersome problem, found out that the solutions that felt better to write has performance issues and that the solution to the bothersome problem is to write code the bothersome way - re-bind functions in the constructor. However, with the transform-class-properties babel plugin's help we can write arrow functions on the class that automatically binds, solving both problems and resulting in better code.