DEV Community

Cover image for Developer Dark Arts: React Class Components
Nate Clark
Nate Clark

Posted on • Edited on • Originally published at codingzeal.com

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, and taught us it was OK to JS all the thingsℒ️. Got JS? Html? Css? Leftover πŸ•? Throw it all in there, it'll blend.

Oh 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.

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>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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" again

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


A Back to the Future meme with a text overlay that reads Where we're going, we don't need classes

🎣 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...

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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 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

ΒΉ 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 (for me). 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.

πŸ“” 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 (read: functions over classes).

Disagree? Love classes? Spot on? Let me know in the comments below.

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

Top comments (0)