![React logo](pictures/React logo.png)
This is the sixth part of my notes on egghead.io's The Beginner's Guide to ReactJS.
This section will show how implicit this
bindings are lost when updating state in React and how to deal with it in different ways. The title of the video would be better named "Setting State and this
".
The this
binding is determined by these rules in order of precedence:
-
If
new
is called,this
is bound to the newly constructed object. -
If
call
orapply
(orbind
) is called,this
is bound to the specified object. -
If a context object owning the call is called,
this
is bound to the context object. -
this
by default isundefined
in strict mode or a global object otherwise.
We will focus on number 3, which is called implicit binding.
This is a vanilla JavaScript example of an implicit binding and the implicit binding being lost.
https://gist.github.com/18db5581c12ed804bff703cbda103607
Although bob
doesn't "own" the function greet
, you could say it does at the time bob.greet
is called. In this situation, we say bob
is the context object and this
is "implicitly" bound to bob
.
The this
binding to bob
is lost when bob.greet
is assigned to the plain function greetFn
because greetFn
is a plain function that references greet
. The default binding is applied and GLOBAL OBJECT NAME
is logged to the console.
It's important to note that even though it greetFn
looks like a reference to bob.greet
, it's actually just a reference to the function greet
.
Let's look at something similar:
https://gist.github.com/b882f366b298806fb168c29849375794
Since there is no global variable "name," nothing is returned.
Now we can understand how something similar happens when updating state in React, and how to deal with it.
We'll start by making a button that increments a number when it's clicked.
https://gist.github.com/a0aa095c00eca13a0d4aed18a88c3a0a
![The initial incrementing button. It works.](gifs/initial counter.gif)
Focus on on the onClick
value. We have an arrow function that increments state with this.setState
.
While this code is completely functional, we could assign the this.setState
function to an event handler for more readability.
The edited code would look like this:
https://gist.github.com/5e94369f92639f3f52197db83bc16c06
![The button does not work](gifs/broken counter.gif)
Uh oh! It doesn't work anymore. If we open the developer's console, we get Uncaught TypeError: Cannot read property 'setState' of undefined
.
this
's binding in this.setState
to the object created by class Counter
was lost when we assigned it to handleClick()
. We are just assigning a reference to the setState
function to handleClick()
. handleClick()
is also just a plain function. Because classes in ES6 are always run in strict mode, this
defaults to undefined
. Notice the parallels from the example above in JavaScript.
We'll cover several ways to ensure the this
binding is not lost.
One way uses "explicit binding," or rule number 2 from the above this
binding rules. This is when we explicitly say what we want this
to bind to with call
, apply
, or bind
.
Here we'll use the built-in bind
utility in the onClick
assignment. bind
returns a new function that is hard-coded to call the original function with the this
context object you specified. This variation of explicit binding involving assigning call
, apply
, or bind
expressions to functions is called "hard binding."
The timer will function properly if we edit onClick
like this:
onClick={this.handleClick.bind(this)}
While this works, this could be a performance bottleneck in some situations.
To deal with the bottleneck we can reference the prototypal method handleClick()
by adding this.handleClick
in the constructor and assiging it a pre-bound handleClick
method.
The contructor would look like this:
https://gist.github.com/4ac318b3f926d62295d81b844b0b80d6
This is the same as the common pattern of lexically capturing this
in pre-ES6 code, like in var self = this;
.
This solution can be cumbersome if we need to do this for a lot of methods.
Let's look at another solution with public class fields. In a nutshell, these let us declare class properties without the constructor.
Look at the code below for before and after public class fields are implemented.
Before:
https://gist.github.com/86ee67a95c097a7b2b775dc27900dd1a
After:
https://gist.github.com/c2a27c462691ea4b0b0559389f184c63
We've moved this.state
and the this.handeClick
assignments out of the constructor and into the class body. this.
is no longer necessary since state
and handleClick
are in the class body. state
and handeClick
are the public class fields and we assign expressions to them. Since the constructor has become the same as the default constructor, we've removed it.
The incrementing counter will work with this implementation.
We could refactor this by removing the handleClick()
method from the prototype and having it only on the instance.
https://gist.github.com/0e167ba3b9793f5627d03e55ddb55a2e
While this works, we have to call the .bind
method everytime we update state. You can use the lexical this
that are possible with arrow functions to workaround this.
https://gist.github.com/a36c37bc5da1af09fa83c74671e7c2ab
We discussed four main ways:
-
Hard binding the method with
.bind
in event handler assignment value -
Lexically capturing the prototype method in the constructor
-
A variation of number 2 using public class fields
-
Using lexical
this
via arrow functions
The issue with number 1 is that it can be a performance bottleneck in some situations. Number 2 can be cumbersome if we have to do it for many methods. Number 3 is currently an experimental implementation; it's probably not good for production code (although it's commonly used in many projects).
Number 4 works and is very common, but it's not without its criticisms. YDKJS author Kyle Simpson says that using lexical this
reinforces a bad practice of evading a solid understanding of this
. He suggests people either stick with lexical style code or embrace this
and avoid lexical this
.
Either way it's important to understand both hard binding and lexical this
so you can understand others' code and adapt to any situation when working in a team.
Be careful of losing the this
binding when updating state in class components. You can keep the binding with either the .bind
method, capturing lexical this
via assignment in the constructor or with public class fields, or with the lexical this
included in arrow functions.
This section will explain how to manipulate the DOM node directly with React's ref
prop. Sometimes this is necessary for some JavaScript libraries to work or when we want to get the value of form fields.
We'll use vanilla-tilt.js as an example on how to make a JavaScript library functional with ref
.
This is our base code:
https://gist.github.com/4268e70d9d0000b2e4f562cf4ad71464
Class Tilt
renders a div with the class tilt-root
, which nests a div with the class tilt-child
, which nests a div that spreads the props. tilt-root
styles one div to have the colored gradient while tilt-child
is a smaller white box. Both also have styles for animations we'll see later.
We then create constant element
, which is the Tilt
component wrapped in a div with the class totally-centered
. There is another div with the same class nested inside of Tilt
with the words "vanilla-tilt.js". totally-centered
are flexbox styles that horizontally and vertically center content.
This code produces this static image:
![Static white box in a colored gradient box.](pictures/static vanilla-tilt.png).
Taken from the React docs,
The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.
When the ref attribute is used on an HTML element, the ref callback receives the underlying DOM element as its argument.
In our code, we'll use the ref
callback to store a reference to our desired DOM node.
The render method now returns this:
https://gist.github.com/4c2aae3060fdf41105bd42b59fac5ee6
We can see we're accessing the node we want with:
https://gist.github.com/562671b87ba5bd981dd21c87a32ab58a
It works!
To manipulate the DOM,
-
Pass on a
ref
on the element that you're rendering. Puttingref
on a class references an instance of that class. -
In the
ref
value, pass in the node as the argument and return an assignment of the node to a value in the instance (this.something
). -
After the component is mounted, the node can be used for a library.