Original text: overreacted. IO/according to – do – we – w…
I hear Hooks are hot these days. Ironically, I’d like to start this article with some interesting stories about class components. What do you think?
The pits in this article are not important for you to use React properly. But if you want to take a closer look at how they work, they’re actually quite interesting.
Let’s start with the first one.
The first thing I wrote in my career was super(props), which I can’t remember:
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
Copy the code
Of course, it is suggested in the Class Fields Proposal that we skip this beginning:
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
Copy the code
This syntax was intended when React 0.13 added support for normal classes in 2015. Defining constructor and calling super(props) is always a temporary solution, perhaps until the class fields provide a less engineering anti-human alternative.
But let’s go back to the previous example, this time using only ES2015 features:
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
Copy the code
Why do we call super? Can I call it? What happens if you have to call prop without passing it? Any other parameters? Let’s try it:
In JavaScript, super refers to the constructor of the parent class. (In our case, it points to the react.componentimplementation.)
Importantly, you cannot use this in the constructor before calling the superclass constructor. JavaScript won’t let you do this:
class Checkbox extends React.Component {
constructor(props) {
// 🔴 can't use 'this' yet
super(props);
// ✅ is now available
this.state = { isOn: true };
}
// ...
}
Copy the code
There’s a good reason why JavaScript forces the parent constructor before using this. Let’s look at the structure of this class:
class Person {
constructor(name) {
this.name = name; }}class PolitePerson extends Person {
constructor(name) {
this.greetColleagues(); // this line of 🔴 is invalid. I will tell you why
super(name);
}
greetColleagues() {
alert('Good morning folks! '); }}Copy the code
If this is allowed before calling super. After some time we might modify greetColleagues and add Person name to the prompt message:
greetColleagues() {
alert('Good morning folks! ');
alert('My name is ' + this.name + ', nice to meet you! ');
}
Copy the code
But we forgot that super() calls this.greetcolleagues () before setting this.name. So this. Name is not defined yet! As you can see, with code like this, it’s hard to figure out what went wrong.
To avoid such pitfalls, JavaScript enforces that if you want to use this in a constructor, you must first call super. Let the parent class finish its work first! This restriction also applies to React components that are defined as classes:
constructor(props) {
super(props);
// ✅ can be used here with 'this'
this.state = { isOn: true };
}
Copy the code
This leaves us with another question: why is the props parameter passed?
You might think it necessary to pass props to super so that the constructor of react.componentcan initialize this.props:
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...}}Copy the code
That’s pretty close to the right answer — in fact, that’s what it does.
But for some reason, even if you call super without passing props, you can still access this.props in render and other methods. (You can try it yourself!)
Why is that? As it turns out, React also allocates props on instances after calling the constructor:
// Inside React
const instance = new YourComponent(props);
instance.props = props;
Copy the code
So even if you forget to pass props to super(), React will still set them later. There’s a reason for that.
When React added support for classes, it didn’t just add support for ES6 classes. Its goal is to support class abstraction as widely as possible. It is not clear how ClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScript, or any other solution has been relatively successful in defining components. So React deliberately doesn’t care if super() needs to be called — even for ES6 classes.
So does that mean you can write super() instead of super(props)?
Probably not, because it’s still confusing. Of course, React will allocate this.props later after your constructor runs, but this. Props will still be undefined between the call to super() and the end of the constructor:
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...}}// Inside your code
class Button extends React.Component {
constructor(props) {
super(a);// 😬 we forgot to pass props
console.log(props); / / ✅ {}
console.log(this.props); / / 😬 undefined
}
// ...
}
Copy the code
If this happens in a method called from a constructor, it can cause a lot of trouble for debugging. That’s why I recommend always calling super(props), even when it’s not necessary:
class Button extends React.Component {
constructor(props) {
super(props); // ✅ passes the props argument
console.log(props); / / ✅ {}
console.log(this.props); / / ✅ {}
}
// ...
}
Copy the code
This ensures that this.props can be set before the constructor exits.
The final point is that React users have long been curious.
You may have noticed that when you use the Context API in a class (either older contextTypes or the newly added contextType API in React 16.6), the Context is passed to the constructor as a second argument.
So why don’t we just say super(props, context)? We could do that, but we use the context less often, so the pit doesn’t really matter that much.
According to the class field proposal, most of these pits will disappear. If there is no explicit constructor, all arguments are passed automatically. This allows you to include references to this.props or this.context in expressions like state = {}, if necessary.
With Hooks, we don’t even have super or this anymore. But that’s a topic for another day.