Using ‘this’ in React Functions

By Amitai B.

Dec 1, 2016

JavaScript (JS) is great but when comparing it to other programming languages one notices it has many confusing parts. One of them is the use of this.

In this blog post, I will explain the right way to use it, and what are the tools that JS provides to help us make use of it. In the end, I will focus on React.

3 types of functions

There three types of functions in JS:

  • Constructor
  • Function
  • Method

Constructor

Constructor is a function that is used to create new object with the same prototype using the new keyword:

function Person() {
  this.name = 'amitai';
}
var newPerson = new Person();

console.log(newPerson.name); // ‘amitai’

As we can see, we used this in order to set a member of Person. When the constructor is invoked, this.name got the value of ‘amitai’. And the object newPerson_had the property _name with the value _amitai.

Function

This is a “function” if it is not a property of an object:

function foo() {
  console.log(this); // undefined in “strict mode” or the global variable
}

In this case, this will be the global variable, or it will be undefined if we opt for “use strict.”

Method

Method is a function that is defined as a property of an object:

var person = {
  fullName: function() {
       return this.firstName + ' ' + this.lastName;
  }
}

person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // ‘Amitai Barnea’

In methods, this is used to refer to other properties of the object; in this case, to firstName and lastName.

So, what’s the problem?

After we understand the differences between the different function types, it is time to describe the problem.

Unlike variables, the “this” keyword does not have a scope, and nested functions do not inherit the “this” value of their caller. If a nested function is invoked as a method, its “this” value is the object it was invoked on. (JavaScript: The Definitive Guide, David Flanagan)

It means that there are times when we expect this will be defined with our object, but it is something completely different!

var person = {
  fullName: function () {
    var calcFullName = function () {
      return this.firstName + ' ' + this.lastName
    }
    return calcFullName();
  }
}
person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // Undefined!!!

The inner function (calcFullName) isn’t aware of this and returns undefined.

This example is not very useful, but shows that it is very common to use inner functions, especially in callbacks, for instance:

var person = {
  fullName: function (cb) {
    $.get('api/getStatus')
    .then(function(data) {
      this.status = data;
    })
  }
}

It will not work, as person.status will be undefined because this was not the object in the callback function.

The solution

There are three solutions to this problem. As JS matured, the solutions have also been improved.

ES3

In ES3 there was no solution to this problem, but there was a work around option:

var person = {
  fullName: function () {
    var that  = this;
    var calcFullName = function () {
      return that.firstName + ' ' + that.lastName
    }
    return calcFullName();
  }
}

person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // 'Amitai Barnea'

Because JS uses closures, we took this and put it into that. that is in the scope of the inner function and the method is working.

ES5

ES5 introduced a more elegant solution using the bind method. The bind method is part of the function prototype. It takes the function and binds it to the object that it has in its arguments.

function addToMe(y) {
  return this.x + y;
}

var num = {
  x: 3
}

var AddToNum = addToMe.bind(num);
console.log(AddToNum(4)); // 7

After the bind option, the function is acting on the object it got.
So how can it solve our problem? We will use bind to connect the inner function to this:

var person = {
  fullName: function () {
    var calcFullName = function () {
      return this.firstName + ' ' + this.lastName
    }
    calcFullName = calcFullName.bind(this);
    return calcFullName();
  }
}

person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // 'Amitai Barnea'

It is working!
Actually, bind is even stronger, and can pass additional parameters to the function. I will not go any deeper on this subject here.

ES6

ES6 fixed this language flaw with its arrow functions. Not only that arrow functions are more elegant, they also inject this into their invocation. Now we can write the code in the following way:

var person = {
  fullName: function () {
    var calcFullName =  () => {
      return this.firstName + ' ' + this.lastName
    }
    return calcFullName();
  }
}

person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // 'Amitai Barnea'

And it is working!

How to imply it in React?

In React, we use components that are classes derived from React.Component. Many times we invoke functions from the component’s controllers that need data either from the component’s props or state by using this.props or this.state.

Because this is an inner function, this is not available in the function. We can solve this problem in two ways:

  1. By using bind in the constructor of the function:
import React, {Component} from 'react';


class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.onSubmit = this.onSubmit.bind(this);
  }
  onSubmit() {
    this.props.submit(this.state)
  }
  render() {
    return (
      <form onSubmit={this.onSubmit}>
        ...
      </form>
    );
  }
}
export default MyComponent;
  1. By using arrow functions:
import React, {Component} from 'react';


class MyComponent extends Component {
  onSubmit = () => {
    this.props.submit(this.state)
  }
  render() {
    return (
      <form onSubmit={this.onSubmit}>
        ...
      </form>
    );
  }
}


export default MyComponent;

Of course, if you are using ES6, use the arrow function to get cleaner and a more elegant solution.

references

  • JavaScript: The Definitive Guide, David Flanagan

Leave a Reply

Your email address will not be published.