Use Class Components with React

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 4 years ago
Updated 2 years ago

In this lesson we'll look at a few ways to deal with issues around this when writing class components with React. We'll eventually land at Public Class Fields syntax which is a stage 3 proposal in the ECMAScript standardization process.

Instructor: [00:00] Here we have a class counter that extends a React component. Inside the constructor, we're forwarding along all the arguments to super and then initializing our state with count zero. Then in our render method, we just render a button that has an on click that updates our state to increment the count. Then it renders our state inside the button.

[00:18] This is totally fine to have an arrow function right here, but sometimes it can get a little bit big. In some cases, it can actually be a performance bottleneck. So we're going to go ahead and extract that. We'll call this handle click on our class here. We're just going to pull this out to our handle click. Then we'll say this.handle click. I'll save it.

[00:39] We'll see that it actually is broken. If I pop open my developer tools, we're going to get this error message every single time saying, "Cannot read property. Set state of undefined." The line that this is breaking on is right here where we're calling set state on this. The problem is pretty fundamental in JavaScript.

[00:56] Let's take an example here. If I were to make a var Bob object and this has a name of Bob and greet that accepts a name, then we return, "Hi, name. My name is this.name." Then we close off that object. Then we say Bob.greet Jane. We're going to say, "Hi, Jane. My name is Bob." What we're doing here though is we're passing on click this reference to this function. So let's go ahead and get a reference to Bob.greet with the greet function equals Bob.greet.

[01:34] Then later on, React is going to come around and call the function that we've passed it. Let's go ahead and do greet function. We'll call it with Jane. We're going to get, "Hi, Jane. My name is blank." That's because when this function called, this is not referencing our object Bob.

[01:51] We can get around this problem in JavaScript by instead assigning this to bind Bob. Now this will always be referencing our Bob function. If we call that again, we're going to see, "Hi, Jane. My name is Bob." We can solve this problem by calling .bind with this. We save. Now our functionality is totally working.

[02:14] That's great. It cleans up our render function a little. But it's kind of annoying to have to call .bind. It also actually is still performance bottleneck in some situations which would be nice to avoid. What we can do is, inside of our constructor, say this.handle click. We reference this.handle click which actually is pointing to the prototypal method here.

[02:35] We're going to actually reassign this to this.handle click.bind this. We're creating new function inside of our constructor and overriding the function that we have reference to from our prototype to a pre-bound method. Now we can just pass this without the bind, and everything works perfectly.

[02:54] That's better. We're getting rid of that performance bottleneck, but this is still super annoying, especially if you have a lot of these.

[03:01] One really neat trick is we can actually use public class fields. Let's go ahead, and we'll remove these and move these down outside of the constructor. Now our constructor looks exactly like the default constructor, so we can get rid of that. This is using public class fields where we have the name of the field and then the assignment and then the value that it'll be assigned to.

[03:20] Now if we save this, everything else is working exactly as it was before. We've gotten rid of a little bit of boiler plate using public class fields. We can actually do a little bit better by not creating this handle click method on the prototype at all and just having it on the instance. Here we can say, "Handle click equals a function that we bind to this." Now we can get rid of that. Everything is working yet again, just perfect.

[03:47] But having this bind on here is super annoying also. So we're going to get rid of that and just use the lexical this that arrow functions give us. Everything is working perfectly there too.

[03:57] Now we've been able to clean up our render method and avoid a potential performance bottleneck by moving our event handler to the class body and using public class fields. We got rid of that constructor. We're using this public class field syntax. Then we're using an arrow function to avoid issues with using this inside of our event handler.

peterschussheim
peterschussheim
~ 4 years ago

Can you explain a bit why you use rest parameters in the class constructor and call to super? Is this to enable Array.protoype methods on the args and collect multiple args into a single array?

I am curious about this since I haven't seen any discussion about this technique and am not clear on its advantages and disadvantages over calling constructor and super without the ellipsis operator.

Great work on these courses!

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

Sure! When I don't care about the arguments (or the rest of the arguments), then I want to avoid future API changes from impacting my component (if args were to be removed or added). So I just forward them all to super without giving them a name. It's basically a way to say: "I don't care about what these args are, but I want them all to go to super"

Luckily, I don't often have a constructor thanks to public class fields :)

Lawrence
Lawrence
~ 4 years ago

I just learned about using public class fields from this video. I must have skimmed past that part in the React documentation and just assumed I had to write bind(this) every time. Thanks for sharing.

Patrick
Patrick
~ 4 years ago

Hi Kent. Great course; I especially like the very gradual explanation of React.createClass vis a vis the different levels of abstraction in Lesson 2 (or 3?). Quick question about this lesson though: you mention that by getting rid of the constructor and using the public class fields, we can get rid of the bind "...by not creating the handleClick method on the prototype and putting it on the instance." I guess what confuses me is that I thought the instance methods/properties were created from the constructor and that everything outside the constructor was on the prototype. However, we have gotten rid of the constructor. Shall I assume that all public class fields are instance methods/properties? Also, what if we had kept the constructor? Would the public class fields still be on the instance or are public class fields in lieu of a constructor? Having wrote all this out, I think I can partially answer my question by realizing I should probably research public class fields! But hey, what's done is done; anything you can do to help is much appreciated! Thanks again and love your youtube stuff as well.

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

Hi Patrick 👋

You can imagine that all the public class fields are actually being initialized as properties on the instance (this) in the constructor. On top of that, when your constructor looks like this:

constructor(...args) {
  super(...args)
}

That's exactly the same as the default constructor that all "constructorless" classes have. So we can remove it and rely on the default constructor. Everything else behaves the same.

So...

Shall I assume that all public class fields are instance methods/properties?

Yes

Also, what if we had kept the constructor?

Nothing would change because our constructor was the same as the default.

Would the public class fields still be on the instance or are public class fields in lieu of a constructor?

Yes, public class fields are always assigned to the instance :)

Osama Jandali
Osama Jandali
~ 4 years ago

Do I have to do any config to use public class fields now ?

Edgard
Edgard
~ 4 years ago

Excellent video! Knowing why things should be done one way or another and it's implications is fantastic!

Rob Wetherall
Rob Wetherall
~ 3 years ago

Why does the setState function on the onClick event need to have parantheses around the bracketed count increment after the fat-arrow? is this always the case or is this a special circumstance?

Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

Hi Rob, This is because otherwise the brackets will be interpreted as the brackets in a block:

const arrow1 = () => {
  // this is a block
}

const arrow2 = () => ({
  // this is an object
})
Alex Okros
Alex Okros
~ 3 years ago

Hey Kent,

Great course! Really learning a bunch.

You might improve this lesson by explaining that arrow functions don't have a this context and they default to looking up one level, until they do find a this to bind to :)

Thanks!

Nick
Nick
~ 2 years ago

Agree with Alex, this is great, but a lesson previous to this explaining these binds would be fantastic.

Ben
Ben
~ 2 years ago

Wow, in 4 minutes you went through all permutations of 'this' bindings in React components like a hot knife through butter

Ben
Ben
~ 2 years ago

Wow, in 4 minutes you went through all permutations of 'this' bindings in React components like a hot knife through butter. Well done!