Binding callbacks in React components
New to React? Please feel free to completely ignore this article. I’ve yet to run into perf issues with what I call “Bad Solution 1” below. I’d advise you to go build some apps first and then come back to this and treat it as React trivia.
Seriously, this isn’t important. Use inline arrow functions, it’ll be fine.
It’s common to pass callbacks to event handlers in a component. Let’s say we want to console.log something every time a user clicks a button.
Lovely, that works just as expected. Now what if we want to call another function inside `handleClick()` called `logPhrase()`?
Well that doesn’t work. We get an error along the lines of
TypeError: this.logPhrase is not a function
at handleClick (file.js:36:12)
When we give the `handleClick` function to `onClick`, we’re just passing a reference to a function. The event handling system is what actually calls your `handleClick` function. Because of this, `handleClick` doesn’t have the same `this` context that we were expecting when we wrote `this.logPhrase()`.
We want `this` to refer to the DankButton component. Here are a few ways to do this.
Bad Solution 1: Arrow functions
Arrow functions were introduced in ES6 and are neat ways to write anonymous functions. They aren’t just syntactic sugar around anonymous functions though. An arrow function does not have its own context and will instead use the same `this` as the context in which it was defined. We can use this fact and pass in an arrow function to the `onClick` callback.
However, I would not recommend solving it like this. Since the arrow function is being defined inside `render`, a new function will get made every time this component is re-rendered. In React, rendering is cheap so re-renders can happen often. This means functions created on previous renders pile up in memory and force the garbage collector to clean them, which is an expensive operation.
Bad Solution 2: this.handleClick.bind(this)
Another way to fix this issue is to bind your callback to the correct `this` context.
This solution has the same problem as the arrow function approach. We’ll create a new function on every render. But how come that happens even though we aren’t using an anonymous function this time? The following snippet holds all the answers.
`.bind` doesn’t modify the original function, it just returns a new function with the given context. (Notice how boundTest and test aren’t equal?) Due to this, the garbage collector still needs to clean up after all your old bound callbacks.
Good Solution: Bind handleClick in the constructor
Using `.bind` did give us what we needed. Now we just need to get around the issue of creating a new function on each render. We can solve this by only binding the callback in the constructor. The key here is that the constructor is only called once, not on every render. This means we aren’t creating a bunch of functions and forcing the garbage collector to clean up.
Sweet, now we have a function that’s bound to the right `this` context and it doesn’t get created on every render! But wait, there’s more!
Good Solution: Arrow function class property
Binding the function in the constructor is fine and dandy but not the most ergonomic. If you have many handlers you’ll need to bind each one in the constructor which is a drag. In his response below, Dwight Ware explains how class properties can help get rid of this boilerplate. Here’s his example:
Class properties are (at the time of writing this) at stage-2 in the proposal process.
If you’re using `React.createClass` instead of ES6 classes, you won’t even run into this problem. `createClass` components automatically bind their functions to the component so `this` always works, even when you pass a callback to an event.
Here are some links you might find interesting: