Before getting to React, it's helpful to know what this does generally in Javascript.
Take the following snippet of code. It's written in ES6 but the principles for this
predate ES6.
class Dog {
constructor() {
this.favoriteWord = "Woof!";
}
bark() {
return this.favoriteWord;
}
}
let dog = new Dog();
dog.bark(); // => Woof!Cool. That makes sense. But remember, functions are also objects in their own right in Javascript, so let's try this:
let bark = dog.bark;
bark(); // => ErrorRuh roh. What happened? You probably got a message saying favoriteWord wasn't
a property of undefined, or something similar. But this.favoriteWord clearly
refers to the Dog instance, so what gives?
The answer is that this is determined at the time the function is called,
not the time the function is defined. This can be super confusing if you're
coming from, say, Python, where the following code works:
class Dog:
def __init__(self):
self.favorite_word = "Woof!"
def bark(self):
return self.favorite_word;
dog = Dog()
bark = dog.bark
bark() # => Woof!It's tempting to say this and self do the same things. They both refer
to the parent of a function in this case. But in the case of Python, self
is determined at the time the function is defined. In the case of Javascript,
this is determined at the time the function is called.
The bark variable is distinct from dog.bark.
To make this a little clearer, let's just use a different name.
let bark2 = dog.bark;
bark2();We're creating a new variable (bark2). bark2 doesn't have a parent at the
time it's called, unlike dog.bark (which has dog as a parent). bark2
is a top level variable, so it's "parent" is undefined. undefined doesn't
have a favoriteWord property, so calling the function results in an exception.
Before a Javascript expert shouts at me, note that in Javascript-land, we
generally think of the relationship between dog and bark not as "parent"
and "child", but that dog is the "context" for bark. dog.bark means
"Call the bark function with the context of dog". And this always refers
to the context in a given function.
To understand why someone (if not necessarily you or me) might think this is cool, consider the following:
let cat = {
favoriteWord: "Meow!"
};
cat.meow = bark;
cat.meow(); // => "Meow!"
bark(); // Nope, still brokenThis works because the this variable in the bark function isn't tied to the
original dog object. So we can freely assign bark to cat.meow. And when
we call cat.meow, the caller is cat, so this.favoriteWord refers to "Meow!"
instead of "Woof!".
As an aside, the "context" for a Javascript function or class method is distinct
from "context" in React.
As the React developers themselves indicate, if you're just getting started with
React, ignore React's version of context. However, you do need to understand
context in the Javascript sense insofar that you're using classes to represent
React components and invoking stuff like this.props or this.setState({ ... }).
OK, let's add a new wrinkle. Consider this now:
let alwaysWoof = bark.bind(dog);
alwaysWoof(); // => "Woof!"Why does this work? It's because calling bind on a function returns a copy of
that function in which this is always set to whatever arg you pass to bind.
This applies even if we change the caller of the bound function:
cat.meow = alwaysWoof;
cat.meow(); // => "Woof!"In the class context, it's pretty common to bind to this:
class ConsistentDog {
constructor() {
this.favoriteWord = "Woof!";
let bark = function() {
return this.favoriteWord;
}
this.bark = bark.bind(this);
}
}
let conDog = new ConsistentDog();
let conBark = conDog.bark;
conBark(); // => "Woof!"Writing .bind(this) over and over is pretty annoying, so in ES6,
you can also avoid writing .bind(this) with the () => ...
syntax:
class SimpleDog {
constructor() {
this.favoriteWord = "Woof!";
this.bark = () => this.favoriteWord;
/*
Or you can do this if you need more than one statement
for your function.
this.bark = () => {
let simpleWord = this.simpleWord;
return simpleWord;
};
*/
}
}
let simDog = new SimpleDog();
let simBark = simDog.bark;
simBark(); // => "Woof!"Still with us? OK, now to bring in React. Consider this React component, defined as an ES6 class:
class Welcome extends React.Component {
render() {
return <button onClick={this.sayName}>Say My Name</button>;
}
sayName() {
alert(this.props.name);
}
}In React, you invoke like this: <Welcome name="Bob" />. This renders a button.
Clicking the button should trigger an alert with "Bob".
Except it doesn't. Because in the above example, this would be undefined in the
sayName function.
What's happening inside the render function is that this refers to the current instance
of our React component. That component has a sayName function defined, so this.sayName
points to our function, just fine and dandy.
But what React is doing behind the scenes is assigning this.sayName to another variable.
That is, it's just like this:
let onClick = this.sayName;
onClick(); // Technically a click event is passed to onClick
// but this doesn't matter for our purposesAnd just like our dog example, we get an error. Because this is undefined. This is
extra confusing because in previous versions of React, React would "autobind" the
event handler for you, so it would work. But at some point, Facebook decided to
stop doing that, so ... here we are.
So how can we fix our component? We just do binding ourselves, like this:
<button onClick={this.sayName.bind(this)}>Say My Name</button>;Or with ES6 syntax:
<button onClick={() => this.sayName()}>Say My Name</button>;And it should work!
One final note -- when we bind a function in React, we can do that not only when the render function is called, but before as well. So take this:
class Welcome extends React.Component {
constructor(props) {
super(props);
this.boundSayName = this.sayName.bind(this);
}
render() {
return <button onClick={this.boundSayName}>Say My Name</button>;
}
sayName() {
alert(this.props.name);
}
}We can do this.boundSayName instead of this.boundSayName.bind(this). Because
this.boundSayName was already bound to this in the constructor.
And that's it! Hope it helps!
Thank you! This cleared some stuff up 😃