loading...

Turning `class App extends React.Component` into a coding lesson

carlmungazi profile image Carl Mungazi Updated on ・5 min read

Ever since I pulled open the hood of Mithril.js in 2017, I have developed an interest in framework and library architecture. Poking around the source made me realise that the authors of these tools use the same language features I used daily, albeit at a much more advanced level. In the early days, when digging into codebases I followed a very passive approach of reading the code, inserting breakpoints here and there, and moving on. This has its benefits in that you can learn a lot by reading well written code. However, there comes a stage when that approach becomes boring or evolves into something more dynamic. So, in this short article I will share some of the ways I actively dig into source code.

The background to this post is that it came from me doing this tutorial. After completing it, I was curious to see how much code was needed to ensure class App extends React.Component worked.

class App extends React.Component {
  state = {
    text: Date.now()
  }

  onButtonClick = () => {
    this.setState(() => ({ text: Date.now() }))
  }

  render() {
    // ...
  }
}

ReactExperimentalRenderer.render(
  <App />, 
  document.getElementById('root')
);

Using the simple app above, I went about my new adventure. Previously, I would have jumped straight into the code but I began by asking myself: What kind of object am I extending when I write class App extends React.Component?. After jotting down some thoughts about expecting to find the setState method and references to the createElement function which turns JSX into React elements, I dove in.

The base class

In the aptly named ReactBaseClasses file you will find the function below. The original function has a lot more comments but I've only left the ones relevant for this article:

function Component (props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = {};
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue ;
}

Component.prototype.isReactComponent = {};

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState')
}

Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
}

//...

export {Component, PureComponent};

You can use this code to come up with a list of questions. The aim is not to create an exhaustive list or to even answer all the questions. Instead, focus on learning how to ask good questions. The questions I came up with were:

  1. Why is Component a function and not an ES6 class?
  2. this.setState calls another function which, according to the comments, is injected by the renderer. How does this happen and how is the updater implemented?
  3. function Component (props, context, updater) { /* ... */ } is what we're extending when we write class App extends React.Component. How does extends work?
  4. Which precedence rule applies to the way we are passing this.updater.enqueueSetState a this binding?

Why is React.Component a function and not an ES6 class?

I could not think of an answer for this so I asked Stack Overflow. The general consensus is that it was done to cater for environments which do not support ES6 classes. I expected a fancier reason but the responses reminded me that every piece of code you comes across does not have to be complicated.

What is the updater?

This is the updater and this is where it is set. Unlike our previous question, this one requires some context. Normally, whenever I come across a rabbit hole I tend to jump in. However, that is not always fruitful because not every rabbit hole needs investigation. What you can do, though, is take a cursory glance at the code in the aforementioned hole and note down topics for future inquiry.

In this instance, you will come across linked lists. This can lead you to articles which explain React's usage of linked lists. You might also come across interesting tidbits whilst researching the usefulness of noop functions like ReactNoopUpdateQueue.

How does the extends keyword work?

In short, the extends keyword is used to create subclasses. In our case, App is a subclass of React.Component. React creates an instance of App and then the fun begins. And again, asking a question such as this leads you to more excellent writing.

What is this doing?

Our click handler looks like this:

onButtonClick = () => {
  this.setState( () => ({ text: Date.now() }) )
}

The setState method like this:

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
}

And this.updater.enqueueSetState like this:

function enqueueSetState(inst, payload, callback) {
  var fiber = get(inst);
  // ...  
}

Updates on a component are managed via the component's corresponding fiber object. At the time of the setState call, the updater property (which is an object) already exists on our App component but we need to ensure enqueueSetState is invoked within the context of App. Fortunately, the this context passed to enqueueSetState is our App component, so React uses it to get a reference to App's fiber object via the get(inst) call.

Also, notice that in our onButtonClick method, we pass an anonymous function as the first argument to this.setState. How is it treated by enqueueSetState? This is how:

partialState = _payload2.call(instance, prevState, nextProps)

Using the .call method gives React a reference to App via the first argument passed in. Unlike the previous usage with enqueueSetState where the this binding was more implicit, using .call makes it more explicit.

What next?

Interrogating source code in this manner is one of the best ways to improve your programming skills. How so? Well, let us list some of things learnt during the process above:

  • The importance of thinking about which environment your code will run in and how this impacts the language features you choose
  • A real life example of linked lists in action
  • Noop functions
  • In-depth reference material on ES6 classes
  • Implicit and explicit ways of binding this

In addition to increase your knowledge, reading the source code of a framework or library you use frequently also helps with things such as debugging or contributing to open source. For example, my first (and only commits) to the React source code have been typo fixes.

Posted on by:

carlmungazi profile

Carl Mungazi

@carlmungazi

Frontend Dev with a penchant for reading source code

Discussion

markdown guide
 

Nice! Would like to see the comparison with functional component.

 

Yes, I never looked at that but just this week I've been adding components to the virtual dom framework I am building. This is how I am handling the components and I imagine React probably does something similar (with more complexity, of course)

// you write your component
function Comp() {
  // ... do stuff here
}

// the framework handles it like so
if (typeof vNode.type === 'function') {
    const Component = vNode.type;
     // ... set the props here
    // ... create update function which is similar to setState

    const renderedComponent = Component(props, updateFn);

    return renderElement(renderedComponent);
  }