How and Why to Bind a Callback Function in React Components

a_reiterer

Andreas Reiterer

Posted on February 9, 2018

How and Why to Bind a Callback Function in React Components

This might sound familiar to you: You run your React app after you made some changes to test your new functionality and get an error message like this: “ this.setState’ is not a function “. It’s probably because you forgot to bind a callback function before passing it down a prop. Today you’ll learn why you need to bind, and how to bind a callback function in React.

Handling the this keyword is causing a lot of headaches for many developers. That’s because in JavaScript it is not always clear what this is actually referring to. Especially, when you’re working with callback functions.

If you ask yourself, why you even have to bind callback functions and when you actually have to do this, this article is for you: Today we’re going to learn about binding the this keyword in React components.

TL;DR: Binding callbacks is a JavaScript thing. It’s necessary because you have to tell your callback what it’s context is. Bind your callbacks in React either in the constructor or by using the public class fields syntax.

Why we Need to Bind: The Basics of this

First of all, it’s not a React thing that you’ve got to bind this. In fact, it’s related to the way JavaScript works. this is a special keyword inside each function that refers to the current context. You might already have seen this in other programming languages.

In JavaScript, however, the value of this depends on how the function was called, not where or when it was defined. Also, it is not affected by scope, like a variable would be. This means, that whenever you pass a function down another function, this will not refer to the same value.

function myFunction() {
 console.log(this);
}

// Calling the function normally
myFunction(); // 'this' will refer to 'window'

// pass the function to an object and call it as an object method
var myObj = { doSomething: myFunction};
myObj.doSomething; // 'this' will refer to 'myObj'

If you want to learn more about this, I recommend having a look at the MDN documentation.

But what does all of that have to do with binding this, you ask? Well – it’s possible to tell a function what it’s this should refer to – and that’s what you’re doing with binding this. Let’s have a look at that.

How to Bind this

If you bind a function, you can set the value of this for later, so it doesn’t matter anymore where exactly your callback function is called. You can achieve this by calling bind(this) for your function:

function myFunction() { 
  console.log(this); 
}

// bind(this) creates a new function where the value of 'this' is fixed
var boundFunction = myFunction.bind(this);

// It's also possible to replace the function by it's bound version
myFunction = myFunction.bind(this);

Calling bind(this) on a function returns a new (bound) function that has the value of this already defined. This way, if you would pass it to another object, or down a prop inside a React component, the value of this inside this component will not change anymore.

Now let’s have a look at how we can bind this inside a React component.

Binding this in a React Component

The best way to show the different approaches is an example. Have a look at the components in the following code sample:

const AddComponent = ({onAdd}) => {
 return (
   <div>
     <button onClick={() => {onAdd("item")}}>Add</button>
   </div>
 );
}

class MyListComponent extends Component {
 constructor(props) {
   super(props);
   this.state = {
     myObjects: []
   }
 }

 handleAdd(newObject) {
   this.setState((prevState) => (
     Object.assign(
       {}, 
       this.state, 
       { myObjects: [...prevState.myObjects, newObject] }
     )
   ));
 }

 render() {
   return (
     <div>
       <AddComponent onAdd={this.handleAdd} />
       {this.state.myObjects}
     </div>
   )
 }
}

What we have here is a simple Component that holds a list of items. A child component adds items to this list with the help of a callback passed through props. So far so good. If you execute the above example, you’ll get an error because this is undefined.

The error you get, when you call a callback function without binding ‘this’

In the following sections, I’m going to explain the recommended ways to bind this to avoid such errors.

Using The Public Class Fields Syntax

It’s worth to note that the class fields syntax are not standardized yet. But they are already so widely used that if there would be syntax changes, it probably won’t take long for a proper frictionless migration strategy to appear.

When using the class field syntax, you’re going to transform your callback to a public field of your class. Doing so will bind the this of the callback automatically to the class it was defined in. This allows you to pass the callback to your child component without having to bind it separately in your constructor.

handleAdd = (newObject) => {
   this.setState((prevState) => (
     Object.assign(
       {}, 
       this.state, 
       { myObjects: [...prevState.myObjects, newObject] }
     )
   ));
 }

Binding this Inside the Constructor

One of the easiest ways to bind this is to do so in your parent component’s constructor by calling .bind(this) for your callback function:

constructor(props) {
  super(props);
  this.state = {
    myObjects: []
  }

  this.handleAdd = this.handleAdd.bind(this);
}

That’s it! Basically, you just have to bind every callback function inside the constructor and you’re safe to go.

If you have a lot of callback functions you can probably imagine how big your constructor could get. If you compare this to using the public class fields syntax, you’ll notice that this approach means a bit more typing for you and a potentially bigger constructor.

Alternatives and Their Caveats

In the previous sections, I explained the recommended ways of binding this. However, if you don’t like the above-mentioned approaches, here are some more – if you are okay with their caveats.

Binding Directly Inside a Prop

Instead of binding the callback function in your constructor, you can do so while passing it through a prop:

<div>
   <AddComponent onAdd={this.handleAdd.bind(this)} />
   {this.state.myObjects}
 </div>

Note: Whenever your component is re-rendered, bind(this) returns a new function and passes it down your component tree. This may lead to unnecessary re-rendering of your child components. Down the road, this can have a massive impact on your app performance.

Passing an arrow function to props

Instead of using the class field syntax, you could directly pass an arrow function down your props:

<div>
  <AddComponent onAdd={(obj) => this.handleAdd(obj)} />
  {this.state.myObjects}
</div>

Note: Like in the previous example, this creates a new callback each time your component is re-rendered and may cause unnecessary re-rendering of your component tree. Again a performance impact that you might not want to put up with.

Wrapping Things Up

We won’t get around binding callback functions, since it’s how JavaScript works. But now that you know why we have to bind this – and how to do it in React components, I hopefully made your life a bit easier.

As always there are different ways to solve this issue. But from the four mentioned approaches, I’d suggest sticking to the recommended options:

  • Using the class field syntax
  • Binding this in the constructor

Doing so might save you from unexpected performance issues. After you get used to using one approach, you won’t even think about it again.

Action Steps

Still not sure what’s the best approach for you? Take some time and try both of them! After you spent some time trying and practicing, you might notice that you like one approach more than the other – you’ll never know just by reading without trying it.

Now get your hands dirty and find your favorite approach!

Stay Updated

Do you want to upgrade your React skills? Stay informed about new articles by subscribing to my Newsletter

💖 💪 🙅 🚩
a_reiterer
Andreas Reiterer

Posted on February 9, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related