React and the mysteries of this

Friday, Dec 1 2017 in react javascript

What’s going on with this in JavaScript and what does bind() do? Let’s explore how this behaves in JavaScript and learn why the React documentation recommends calling bind() on some methods in your class component constructors.

When this confuses you

Let’s begin with an example. Your component displays a button which should update the text in the <output> element when clicked.

class MyAwesomeButton extends React.Component {
  constructor() {
    super();
    this.state = { message: 'Not clicked yet' };
  }

  showConfirmation() {
    this.setState({ message: 'Clicked!' });
  }
  
  render() {
    return (
      <form>
        <output>{this.state.message}</output>
        <button onClick={this.showConfirmation}>Click Me</button>
      </form>
    );
  }
}

You can run this example in this pen. constructor() sets up the initial message by assigning { message: 'Not clicked yet' } to this.state. The render() method places this.state.message in an <output> HTML element.

showConfirmation() updates this.state.message by calling setState(). To invoke showConfirmation() as an event handler when the user clicks the button, we pass this.showConfirmation as the onClick prop to the <button> element,

But when you click the button, nothing happens. Instead, an ominous message appears in the browser console:

Error: this is undefined 

Uh? In other languages, this lets you always access instance fields like state. But while calling showConfirmation() with this works, accessing this.state doesn’t. Why?

How this works

Let’s study a JavaScript function in isolation:

function sayName() {
  return this.firstName;
}

It’s legal to use this in any function, so we can define sayName() outside of a class. You can run the code in this pen. What’s going to happen if we call sayName()?

sayName(); // error firstName is not defined

Outside modules and classes, this is the window object and sayName() prints the value of window.firstName. Since window.firstName does not exist, we’re getting an error, but you can change that if you assign a string to window.firstName.

window.firstName = 'George';

sayName() returns 'George'.

Now sayName() prints the value that you assigned to window.firstName.

If you call sayName() differently, you can also change the value of this. For example, you can make sayName() output 'Henry' by first defining an object where the firstName property points to the 'Henry' string:

const henry = { firstName: "Henry" };

then assigning sayName() to a field on the henry object:

henry.talk = sayName;

When you invoke talk() on henry, this points to henry, so it returns 'Henry', even though talk() is the same function as sayName().

henry.talk(); // Henry

If you call a function through an object field, this points to the object. Methods defined inside a class behave similarly to functions defined on the object instance. For example, create a Henry class and, in the constructor, assign the value 'Henry' to this.firstName.

class Henry {
  constructor() {
    this.firstName = 'Henry';
  }
  
  talk() {
    return this.firstName;
  }
}

When you instantiate the Henry class, JavaScript automatically creates a firstName property on the henry2 object with value 'Henry'.

const henry2 = new Henry();

Calling henry.talk() then automatically references the firstName property on the henry object.

henry2.talk(); // returns 'Henry'

When you use React class components, it’s as if React created an instance of our component classes and called the class methods on the object instance. But if you define a method inside a class and you don’t call it on an object directly, then this is still undefined.

Let’s try it with our talk() method on the Henry class. First define a function that calls another function:

function callOther(otherFunction) {
  otherFunction.call();
}

In JavaScript, functions are also objects that you can pass around and call with the call() method. Try passing henry2.talk() to callOther():

/* error: this is undefined */
callOther(henry2.talk); 

Even though you’ve defined talk() on the henry object, when callOther() invokes talk(), it does not use the henry2 object, so this doesn’t point to henry2. On top of that, you defined talk() as a class method, so talk() runs in strict mode: therefore this ends up undefined.

You can even use call() to make this inside a function point to an arbitrary object.

//  returns 'George'
sayName.call({ firstName: 'George' });

The code that calls the function determines the value of this, even if you originally defined the function in an object or class. Since React does not take care of binding this to the component instance inside an event handler, this ends up undefined.

What if you want this inside event handlers to always point to the component instance?

Solve this!

When you need to update a component state, you need a reference to the component instance. Unfortunately, by the time React calls the event handler, this doesn’t point to the component instance anymore. The code inside modules and classes runs in strict mode, which prevents this from defaulting to the window object, so this ends up undefined.

There are three ways you can force this to point forever to the value that this had when you defined the function, so you’ll be able to access this.state in event handlers.

The first solution is the bind() method. Calling bind() on a function makes this inside the function forever point to the bind() argument. If we call bind() on the sayName() function with henry as an argument, bind() returns a new function, where this always points to henry.

const boundSayName = sayNameBind(henry);

 // returns 'Henry'
boundSayName();

//  still returns 'Henry' 
boundSayName.call({ firstName: 'Tamara' });

bind() can fix our button component. Call bind() on the this.showConfirmation in the constructor:

  constructor() {
    super();
    this.state = { message: '' };
    this.showConfirmation = this.showConfirmation.bind(this);
  }

bind() creates a new function where this forever points to the value of this in the constructor. Since this in the constructor points to the component instance, this inside showConfirmation() will also always point to the component instance. Notice that when you assign the return value of bind() to this.showConfirmation, you replace the original showConfirmation() with the new function.

Another solution is replacing methods with arrow functions. Arrow functions, defined with an arrow (=>) instead of the function keyword, always keep the value of this to whatever it was at the place where you defined the function. For example, let’s create a top-level sayNameArrow() function in the browser console:

const sayNameArrow = () => this.firstName;

Since an arrow function preserves the value this had at the moment the function was defined, and this points to window when we define sayNameArrow, even if we assign sayNameArrow() to an object field, it always returns window.firstName:

henry.talkArrow = sayNameArrow;

// returns window.firstName
henry.talkArrow();

// also returns window.firstName
talkArrow.call({ firstName: 'George' });

Instead of using methods, we can define the event handlers as arrow functions in the constructor:

class MyAwesomeButton extends React.Component {
  constructor() {
    super();
    this.state = { message: 'Not clicked yet' };
    this.showConfirmation =  () => {
      this.setState({ message: 'Clicked!' });
    };
  }

The last solution, class properties, has not yet reached the JavaScript standard, but it’s already a candidate recommendation at the time of writing. Class properties let you create any fields by placing their name followed by a colon in the class body:

class MyAwesomeButton {
  showConfirmation: () => {
    this.setState({ message: 'Clicked!' });
  }
}

This is the same as creating an arrow function in the constructor, but it makes the code easier to parse as you don’t need to look at the code in the constructor to understand what’s going on.

If you use Babel, you can enable support for class properties. Class properties are already enabled if you use create-react-app to manage your application.

All three solutions create a function on the component instance, where this always points to the component instance itself. It would be interesting if you could access the component state without the need to access the instance, maybe with an argument that React would pass to all event handlers, but that’s not possible with the current React version, so we need these workarounds to fix this.

If you want to learn more about this, check Kyle Simpson’s book and if you want to learn more about React, check out my book React for Real.