Developer Dark Arts: React Class Components

As part of the ES2015 release, classes were formally introduced to native JavaScript as syntactic sugar for prototypical inheritance. Object oriented developers everywhere popped champagne and celebrated in the streets. I was not one of those developers.


## 🌄 The Web Landscape


Coincidentally, this was also the time that the JavaScript community was being introduced to React. A library that unabashedly pushed its way past existing modular library seemingly overnight. React's creators took lessons learned from Angular 1.x, introduced [jsx](https://reactjs.org/docs/introducing-jsx.html), and taught us it was OK to _JS all the things™️._ Got JS? Html? Css? Leftover 🍕? Throw it all in there, it'll blend.


## 🎓 Stay Classy


Classes provided a nice cork board for React to pin their patterns to. What is the recipe for a React class component you ask?


1. Create a new file

2. Write a class that _extends_ `React.Component`

3. Repeat


Not much to it. Easy peasy one two threezy. This pattern really flattened the curve for developers learning React. Especially those coming from object oriented languages.


Everyone take a moment and wave 👋 hi to their old friend Readability. As with any new framework, adoption is strongly coupled to readability. React's high readability resulted in most code samples being comprised of classes. Hello world, todo app tutorials, learning resources, Stack Overflow, coding videos; classes as far as the eye can see.


## 🤷‍♂️ So What's the Problem


For the most part, everything was peachy _in the beginning._ We had well-defined class components. We had modular, testable pieces of functionality. Life was good. However we know all good things must come to an end. As your React project's codebase grows you realize you're having to write a fair amount of boilerplate.


```javascript

import React from 'react';


const MIN_POWER_TO_TIME_TRAVEL = 1.21;

const MIN_SPEED_TO_TIME_TRAVEL = 88;


class DeLorean extends React.Component {

 constructor() {

   super();

   this.state = { gigawatts: 0 };

 }


 static const MIN_POWER_TO_TIME_TRAVEL = 1.21;

 static const MIN_SPEED_TO_TIME_TRAVEL = 88;


 componentDidUpdate() {

   const { isLightingStriking } = this.props;


   if (isLightingStriking) {

     this.setState({ gigawatts: DeLorean.MIN_POWER_TO_TIME_TRAVEL });

   } else {

     this.setState({ gigawatts: 0 });

   }

 }


 hasEnoughPower(gigawatts) {

   return gigawatts >= DeLorean.MIN_POWER_TO_TIME_TRAVEL;

 }


 hasEnoughSpeed(mph) {

   return mph >= DeLorean.MIN_SPEED_TO_TIME_TRAVEL;

 }


 render() {

   const canTimeTravel =

     this.hasEnoughPower(this.state.gigawatts) &&

     this.hasEnoughSpeed(this.props.mph);


   if (!canTimeTravel) return <span>🚙</span>;


   return (

     <div title="Great Scott!">

       <span>🔥</span>

       <span>

         {gigawatts} GW / {mph} mph

       </span>

       <span>🚙</span>

       <span>🔥</span>

     </div>

   );

 }

}

```


_NOTE: I am fully aware that this component's implementation is not perfect, but it is typical._


Do you see the `class ... extends React`, `constructor`, `super()`, `render()` lines? These will be needed in every class component you write. My wrists hurt thinking about all the redundant keystrokes. If you don't think lines of code are important, try wrapping your head around a 1000+ line component file. Es no bueno 👎.


Inevitably you will find yourself debugging your new shiny component because it explodes for one reason or another.


- Did you forgot to add the `constructor` method?

- Did you call `super()`?

- Should you be using some other lifecycle method?

 - `componentDidMount`

 - `componentWillMount`

 - `componentRedundantPrefixMethod`

- ...or other undocumented/unstable method?

- How are you going to test the `hasEnoughPower` and `hasEnoughSpeed` methods?

- Wtf is `static`?

- Oh no, not ["this"](https://medium.com/javascript-in-plain-english/the-most-confusing-thing-in-javascript-the-this-keyword-3436f451fca0) again


I realize these all are not necessarily issues with classes, but our React class components aren't as perfect as we first thought.


## 🎣 Enter Hooks

If we fast forward a few minor versions of React we get a shiny new feature called `hooks`. One of the key benefits of hooks is that they allow us to leverage all of the component lifecycle methods **in functional components**. No weird syntax or boilerplate code required.


Here's the hook-ified version of our stainless steel class component...


```javascript

import React, { useEffect, useState } from 'react';


const MIN_POWER_TO_TIME_TRAVEL = 1.21;

const MIN_SPEED_TO_TIME_TRAVEL = 88;


const hasEnoughPower = (gigawatts) => gigawatts >= MIN_POWER_TO_TIME_TRAVEL;

const hasEnoughSpeed = (mph) => mph >= MIN_SPEED_TO_TIME_TRAVEL;


const DeLorean = ({ isLightingStriking, mph }) => {

 const [gigawatts, setGigawatts] = useState(0);


 useEffect(() => {

   if (isLightningStriking) {

     setGigawatts(MIN_POWER_TO_TIME_TRAVEL);

   } else {

     setGigawatts(0);

   }

 }, [isLightingStriking]);


 const canTimeTravel = hasEnoughPower(gigawatts) && hasEnoughSpeed(mph);


 if (!canTimeTravel) return <span>🚙</span>;


 return (

   <div title="Great Scott!">

     <span>🔥</span>

     <span>

       {gigawatts} GW / {mph} mph

     </span>

     <span>🚙</span>

     <span>🔥</span>

   </div>

 );

};

```


There's a lot going on here, especially if you haven't used hooks before. I suggest you take a few minutes to skim through [React's hook documentation](https://reactjs.org/docs/hooks-overview.html) to get familiar if you aren't already.


The key takeaways are:


- We can export and test `hasEnoughPower` and `hasEnoughSpeed` methods _without_ adding boilerplate¹

- We reduced our total lines of code by ~10 (25% less)

- No more `this` keyword

- Boilerplate, "I-only-put-this-in-because-it-won't-work-without-it" code is completely removed

- **We're back to using functional composition in a functional language**

- Functional components are smaller, [more so when minified](https://twitter.com/dan_abramov/status/1055702961303642112)


_¹I know we could have exported those two methods in the class example, but in my experience this is how I've seen the majority of components implemented. Where everything is a class method and accessed by `this`_


## 📜 What If I Am Using Typescript?


**WARNING: Strong opinions lie ahead...**


This post is about increasing readability and writing less code with better test coverage by specifically avoiding the use of classes.


My current opinion of Typescript is that it increases lines of code, reduces velocity, and fully embraces inheritance. It forces OOP patterns into a functional language in exchange for type checking. Hold on, I have to go write some typings... Where was I? Oh yeah, getting lost in context switching 😉.


If you are stuck writing Typescript I'm sorry and I feel for you. I've been there and it was not enjoyable. Stop reading this post as it might tap into the well of stress and development frustration you have tried so hard to ignore.


**Now back to our regularly scheduled post...**


## 📐 Exceptions to Every Rule


As of writing, there are still a few places that classes are a necessary evil. These are considered very niche and make up a very small subset of use cases in most projects.


- When extending `Error` into [custom errors](https://javascript.info/custom-errors)

- When using React's `Suspense`, classes useful for capturing errors in [error boundaries](https://reactjs.org/docs/error-boundaries.html)


## 📔 Where Does this Leave Us?


I hope/speculate that classes will eventually be exiled to outer reaches of the JS community, a la `generators`. Neat to show off in academia with very few real world use cases.


React is already migrating that way. Don't take my word for it, take a look at their documentation. Their examples are mostly functional components with footnotes for class versions. They've even posted a formal statement that [they prefer composition over inheritance](https://reactjs.org/docs/composition-vs-inheritance.html) (read: functions over classes).


---


_Today's post was brought to you by VSCode's "duplicate line(s) above/below" shortcut: `Shift+Option+(UpArrow|DownArrow)`_