I hear that Hooks are now hot. Ironically, I want to start this blog by describing the facts about class. How does that work?

These pits are not important for efficient use of React. But if you want a deeper understanding of how things work, you might find them interesting.

This is the first one.


In my life I wrote super(props)

I’ve written more super(props) in my life than I know:

class Checkbox extends React.Component {
    constructor(props) {
        super(props);
        this.state = { isOn: true}; }}// ...
Copy the code

Of course, the class-related proposal lets us skip this ritual:

class Checkbox extends React.Component {
    state = { isOn: true };
    // ... 
}
Copy the code

Such a syntax was planned in 2015 when React 0.13 added support for pure classes. Defining the constructor function and calling super(props) has been a temporary solution until the class fields provide an ergonomic alternative.

But let’s go back to the example and just use the ES2015 feature:

class Checkbox extends React.Component {
    construtor(props) {
        super(props);
        this.state = { isOn: true };
    }
    // ...
}
Copy the code

Why do we call super? Can we not call it? What happens if we have to call it without passing the props argument? Are there any other parameters? Let’s find out.

Why do we call super? What happens if you call it incorrectly?


In JavaScript, super refers to the constructor of the parent class (in our case, it points to the React.componentimplementation).

Importantly, you should not use this until you have called the parent’s constructor. JavaScript won’t let you do that:

class Checkbox extends React.Component {
  constructor(props) {
    // 🔴 Can’t use `this` yet
    super(props);
    // ✅ Now it’s okay though
    this.state = { isOn: true };
  }
  // ...
}
Copy the code

There’s a good reason why JavaScript forces the parent constructor to run before you access this. Consider a class hierarchy:

class Person {
    constructor(name) {
        this.name = name; }}class PolitePerson extends Person {
    constructor(name) {
        this.greetColleagues(); // 🔴 This is disallowed, read below why
        super(name);
    }
    greetColleagues() {
        alert('Good morning forks! '); }}Copy the code

Imagine that this is allowed to be called before super. One month later, we may change the information contained in Person in greetColleagues:

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.greetemployees() before it has a chance to set this.name, so this.name is not yet defined. As you can see, it’s hard to think about code like this.

To avoid such a pit, JavaScript forces you to call super before using this. Let the parent do its job! This restriction also applies to React components defined as classes:

construtor(props) {
    super(props);
    // ✅ Okay to use `this` now
    this.state = { isOn: true };
}
Copy the code

There’s another question: Why pass props?


You might think it would be necessary to pass props to super, so that the react.componentconstructor should initialize this.props:

// Inside React
class Component {
  constructor(props) {
    this.props = props;
    // ...}}Copy the code

That’s not far from the truth — indeed, it is.

However, even if you call super without props, you can still get props in Render or some other method. (If you don’t believe me, try it yourself!)

How does that work? React also initializes props on instances immediately 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 set them immediately. There’s a reason for that.

When React added support for classes, it didn’t just support ES6 classes. The goal is to support the broadest possible class abstraction. It is unclear what use ClojureScript, CoffeeScript, ES6, Fable, Scala. Js, TypeScript, or other ways to define components will have much success. As a result, React is deliberately unclear about whether super() needs to be called — even though ES6 classes do.

So does that mean you can use super() instead of super(props)?

Probably not because it’s still confusing. Of course, React will assign this. Props after you run constructor. But between the super call and the end of the constructor, this.props is still undefined:

// Inside React
class Component {
    construtor(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, debugging can be more difficult. That’s why I always recommend passing super(props), although it’s not required:

class Button extends React.Component {
    constructor(props) {
        super(props); // ✅ We passed props
        console.log(props);      / / ✅ {}
        console.log(this.props); / / ✅ {}}}Copy the code

Make sure this. Props is set before constructor exists.


One last point React users might be curious about.

You may have noticed that when using the Context API in a class (including old contextTypes and the modern contextType API added in React16.6), Context is passed to constructor as the second argument.

Why don’t we say super(props, context) instead? We can, but the context is used less frequently, so this pit doesn’t come up very often.

With the class field proposal, this pit basically disappears. If there is no explicit constructor, all arguments are passed automatically. That’s why expressions like state ={} are allowed to contain references to this.props or this.context.

With Hooks, we won’t even use super or this. But that’s a topic for another time.

IO/Why-do-we-W… by Dan Abramov