A Hooks-vs-Classes Report Card
Tags: react, javascript, hooks, oop
Published: 2020-04-11 06:32:08

Hooks have been out now for a year-and-a-half.  Maybe I'm late to the game, but I've only been using them heavily in the last few months.  Part of my hesitance was in some of the blatant misinformation that I read on many blogs (and even... _in the official docs_).  

At this point, I don't claim to be any kind of "Hooks expert".  I'll freely admit that there's much about the pattern that I have yet to fully _grok_.  But in short order, I can confidently claim that I've been writing many _thousands_ of LoC - both in brand-spankin-new Hooks-based components, and in old class-based components that I've converted to Hooks.  So at this point, I'm thinking that some of you might benefit from the conclusions that I've recently come to (and some that I'm still slinging around in my head).  

I'm no "class hater".  I don't subscribe to any of the silly dictates that many JavaScript devs use to dismiss class-based components out-of-hand.  If you look through any of the other articles in this series, that basic fact will be obvious to you.  

But I'm not interested in being a "Hooks hater" either.  One of my common refrains is that all of these programming constructs are _tools_.  And dismissing Hooks (or classes) because of some mindless dogma that you read on some "thought leader's" blog post is just as silly as throwing out your hammer (or _shaming other people_ for using a hammer) just because you've decided that every job should be accomplished solely with a screwdriver.

So without further ado, I'm gonna try to compare some of the major advantages that others claim to see in Hooks versus class-based components (and vice versa).  

If you're already firmly in the camp of the "Hooks fanboys" or the "class fanboys", I have no doubt that you're gonna disagree - _vehemently_ - with some of my conclusions.  That's OK.  You won't be the first person to think I'm an idiot - and you won't be the last.

## Code Size

**Classes: B-**
**Hooks: B+**

One of the things that inspired me to write this post is the fact that sooooo many of the Functional-Programming Evangelists seem to talk about functions (and Hooks-based components) as though they are - hands-down - a faster, cleaner, more-efficient way to write code.  After putting in about 30k LoC in Hooks-based development, I gotta tell ya that... I'm just not seeing it.

When converting class-based components to Hooks, I've noticed that, _sometimes_, the Hooks-based equivalent comes out being _a little_ shorter.  But it's hardly a clear win for Hooks.  

Even worse, in many of the "how to use Hooks" tutorials I've seen, they use some kinda loaded example where they seem to purposely write the class-based component in a sloppy, verbose manner.  Then they convert it to some kinda slimmer version in Hooks and they pat themselves on the back about the supposedly-obvious improvement.

For example, they often show code snippets like this:

```javascript
// the evil class-based component
export default class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {counter: 0};
    this.increment = this.increment.bind(this);
  }

  increment {
    this.setState((prevState) => {
      return {counter: prevState.counter + 1};
    });
  }

  render {
    return (
      <>
        <div>The counter is: {this.state.counter}</div>
        <button onClick={this.increment}>Increment</button>
      </>
    );
  }
}

// the oh-so-superior Hooks-based component
export default function Foo() {
  const [counter, setCounter] = useState(0);
  return (
    <>
      <div>The counter is: {counter}</div>
      <button onClick={() => setCounter(counter + 1)}>Increment</button>
    </>
  );
}
```

> Ehr-mah-gerd!  The Hooks-based component is such a _clear_ advancement over that mean, old, ugly class-based component!!!

And that comparison makes perfect sense - if you haven't written a line of React code since 2014.  

Of course, there's no reason to write that bloated class-based component shown above.  We don't have to bind functions.  We don't have to use constructors.  We don't even have to use a standalone update function.  Instead, it's perfectly valid to write the class-based component like this:

```javascript
export default class Foo extends React.Component {
  state = {counter: 0};
  render = () => {
    return (
      <>
        <div>The counter is: {this.state.counter}</div>
        <button 
          onClick={() => this.setState(state => ({counter: state.counter + 1}))}
        >Increment</button>
      </>
    );
  };
}
```
The Hooks-based component is _a little_ smaller.  And I'd be the first to admit that the inline call to `this.setState()` in the streamlined version of the class is... _a bit_ unwieldy.   

But the point is that it's far from a clear-cut, hands-down victory for Hooks.  In classes, you can't avoid defining a separate `render()` function (which adds _two whole lines of code_!!!).  And class-based components, even in the best scenario are _a little bit_ longer on average.  But the rhetoric around Hooks being soooo much shorter/cleaner/prettier is just way overblown.

Here's another silly little "cheat" that I see in some of these online examples:

```javascript
// the evil class-based component
export default class Foo extends React.Component {
  state = {counter: 0};

  doSomething = () => {
    // all the hairy doSomething() logic
  }

  doAnotherThing = () => {
    // all the hairy doAnotherThing() logic
  }

  doSomethingElse = () => {
    // all the hairy doSomethingElse() logic
  }

  render = () => {
    return <div>The counter is: {this.state.counter}</div>;
  };
}

// the oh-so-superior Hooks-based component

const doSomething = () => {
  // all the hairy doSomething() logic
}

const doAnotherThing = () => {
  // all the hairy doAnotherThing() logic
}

const doSomethingElse = () => {
  // all the hairy doSomethingElse() logic
}

export default function Foo() {
  const [counter, setCounter] = useState(0);
  return <div>The counter is: {counter}</div>;
}
```

> Ehr-mah-gerd!  The Hooks-based component is soooo _tiny_.  That's awesome!!

Umm... yeah.  It's only "tiny" because you've exported all the necessary supporting functions outside the component.  And BTW... _you can do the exact same thing with class-based components_.

If you think this is a silly example, I assure you that I've seen very similar examples where someone is trying to "prove" the superiority of Hooks.  I'm not going to blog-shame anyone by linking to them here.  But I'm sure you can find them if you look hard enough.

## Shared-State Management

**Classes: B+ (C)**
**Hooks: A-**

My similar grades might confuse some people.  I've heard a lot of chatter about the supposedly-epic ability of Hooks to share state.  But what I've noticed is that those people rarely make a distinction between sharing stateful _logic_, and simply sharing _state_.

The Hooks documentation itself is very clear on this point.  It states:

> **Do two components using the same Hook share state?** No. Custom Hooks are a mechanism to reuse _stateful logic_ (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.

IMHO, there's a lot of confusion out there on this point.  People talk about shared state in Hooks like you just need to spin up a `useMagicSharedState()` Hook - and it works like Redux, or MobX, or any other third-party state-management solution.

So why do I give Hooks a lofty "A-" on this point?  For two reasons:

1. Hooks have a much cleaner, far-more-intuitive way to use _context_.  This is especially true when you're trying to use _multiple contexts_ in a single component.  With Hooks, you just throw out that `useContext()` Hook, for as many contexts as you need to consume, and it just basically... works.

1. Hooks _do_ have some amazing capabilities to create _true, global, shared state_.  You can do this with custom Hooks - but it's not immediately evident how to make it work from their documentation.  If you want the details on that, you can check it out here: https://dev.to/bytebodger/hacking-react-hooks-shared-global-state-553b

To be frank, class-based components aren't really that far behind.  I give them a "B+" because the new(ish) Context API is, IMHO, extremely powerful and vastly underused in most modern React applications.  It's not nearly as cut-and-paste as Hooks, but I explained one way to fully leverage these capabilities in this post: https://dev.to/bytebodger/a-context-api-framework-for-react-state-management-1m8a

However, I give class-based components a parenthetical "C" in this category because most dev teams _aren't using_, or are _barely using_ the Context API.  And they're usually afraid to pass state through props if it involves more than one or two layers.  

This means that most class-based React applications are highly convoluted by additional state management tools.  I've actually come to the conclusion that nearly _all_ state-management libraries are a _code smell_.  But I have a particular distaste for Redux.  It's no accident that _Redux_ rhymes with _sucks_...

[**Editor's Note:**  At this point, Adam went off on a 10,000-word diatribe about his deep-seated hatred for Redux.  If you've read _anything_ else from him before, this is already "old news" to you.  I cut out all of the Redux Sucks Tome for easier reading.  You're welcome...]

## Legacy Compatibility

**Classes: A+**
**Hooks: C+**

OK, maybe this is an unfair comparison.  Unless you want to write all your components with `React.createComponent()`, classes _are_ legacy.  So _of course_ they're "legacy compatible".  

But Hooks deserves at least some criticism for the fact that they don't always easily integrate with class-based components.  Yes... _I know_ that the Hooks documentation touts them as being perfectly backwards compatible.  And they take great pains to state you can build Hooks-based components right alongside your old, smelly, class-based components.

The problem I've found is mainly in dealing with third-party (i.e., NPM) packages.  Whenever I'm considering using a new package nowadays (or when I'm considering _upgrading_ an existing package), I have to look carefully at the documentation to see _how_ I'm expected to implement it.

A Hooks-based component is still just _a component_.  So if I need to `import` that component and then plop it in the middle of a `render()`, that tends to work just fine.  But I've noticed a disconcerting number of packages where they require me to leverage the Hooks _directly_ - not just the Hooks-based _components_.  And when you do that... your console starts throwing all those errors that happen whenever you try to leverage a Hook directly from within a class.

## Lifecycle Management

**Classes: B-**
**Hooks: F**

Maybe you're thinking that this is also an unfair comparison.  After all, Hooks-based components are all _functions_.  And functions have no "lifecycle".  You just call them and... they run.

But let's get real here.  When you're writing Hooks-based components, you may be using a `function` keyword.  Or you may be using the arrow syntax.  But under the covers, that component's not _really_ running like a "true" function.  

Every React component is ultimately a slave to the virtual DOM.  In theory, the virtual DOM is the "secret sauce" that makes React do all those cool, nifty things without you having to manually program all of the event handlers to make them work.  But this ultimately means that you never have full _control_ of a component.  It will always be beholden to the rendering cycle that's central to React's magic.

But that rendering cycle means that your "function" is going to be called, repeatedly, without you ever having manually triggered it.  This means that, whether you want to admit it or not, _all React components have an inherent lifecycle_.  And yes, that includes Hooks-based components.

Writing Hooks-based components can be downright simple and pleasurable - for a large portion of your codebase.  But if your app is doing anything more than cranking out "Hello World!" messages, at some point, you _will_ find yourself fretting over component lifecycles. 

This is where I find Hooks to be borderline-hideous.  You start reading (and re-reading) all the Hooks documentation for "the Hooks equivalent of lifecycleMethodX()".  And then you start realizing that, for many of those lifecycle methods, the Hooks equivalents are... clunky.  In the worst scenarios, _they simply don't exist at all_.

This isn't to imply that the "normal" lifecycle methods in class-based components are "fun" or "easy".  In any sizable application, lifecycle management is basically a necessary evil.  It can be frustrating.  It can be a source of nasty bugs.  But it _is_ necessary.  

Hooks attempt to address most of this with `useEffect()`.  Yeah... good luck with that.  Pretty soon, you have too many _effects_, and your dependency array is starting to scroll off the right side of your IDE.  And once you start reaching for `useCallback()`...???  _Oh, vey._

In class-based components, I've rarely _ever_ had a problem with _infinite renders_.  Since I've been diving into Hooks, I've already lost track of how many times I've accidentally spawned the Infinite Render Beast while I'm trying to code (what seems to me like) a simple bit of "calculate X, then render Y" logic, or "render X, then do Thing Y".  

## Developer Cognition

**Classes: C**
**Hooks: C**

_No one_ is going to agree with both of those grades.  I give them both a "C" because I've (finally) learned that your perception of Hooks-vs-classes probably says a lot more about your background as a developer than it does about any putative benefits of the tools themselves.

Did you first learn to code in the last half-decade or so?  Do you _only_ code in JavaScript?  (And I'm not implying that there's anything "wrong" with that, if you do.)  Did you get your first programming experience at a _code camp_?  

If you answered "yes" to any of those questions, there's a strong possibility that Hooks "feel" more logical to you.  It took me quite a while to finally grasp this reality, but the latest generation of frontend/JS-only (or JS-centric) devs just seems to have some kind of mental block when it comes to that nasty, scary, dirty `class` keyword.  For them, `class` is the algorithmic equivalent of "moist".

If you're more like me:  If you've got a little grey in your beard. (Who am I kidding?  My beard's almost _totally_ grey.)  If JavaScript is just _one of a library of languages_ in which you're comfortable.  If you've seen the good, the bad, and the ugly of object-oriented programming.  If you're perfectly comfortable writing your own SQL queries.  Or if you've ever had to fret over _memory management_ in an application.  

If you're in _that_ category, there's a decent chance that either: A) You're perfectly fine with the `class` keyword for what it is - _syntactic sugar_.  Or, B) you don't _love_ JavaScript's "faux classes" - but you've learned to accept and master them as just another _tool_ in your toolbelt.  

A practical example of this dichotomy lies in the `this` keyword.  If you've been doing JavaScript for long enough, you have some war stories about the horrible ambiguities that can arise from `this`.  But ever since the introduction of `const` and `let`, I can't honestly _remember_ the last time that I had to track down (or the last time that I _created_) some maddening bug caused by the ambiguities of `this`.

But a recent comment on one of my other posts made me realize that this isn't the case for all JavaScript devs.  Some of them are literally _confused_ by the mere presence of `this`.  

To me, it's dead simple.  `this` just refers to... _this component_.  More specifically, `this` refers back to the _class_ in which this code is written.  I don't honestly understand what's so confusing about that - but I now realize that, for some devs, it absolutely _is_ confusing.

## Adherence to "Standards and Conventions"

**Classes: B+**
**Hooks: C**

Oh, boy.  If you're in deep romantic love with JavaScript, and _functional programming_, and you're still reading, then you're probably having a coronary with this grade.  

> How can you possibly claim that JavaScript _classes_ are more standards-compliant than functions???  JavaScript is a _functional programming language_!!!

First, calm down for a minute.  Take a walk around the block.  Monitor your heart rate.  Have a beer (or three).  It'll be OK...

At some point in the future I'm gonna crank out a (too) long, annoying post about the silly way that some JavaScript devs have glommed onto the whole catchphrase of "functional programming".  But I'm gonna put that one on a shelf for a while...

Let's look at this from the perspective of some really _old_, really _solid_ programming wisdom.  The first is an incredibly simple concept that served me incredibly well when I was a younger developer - and it still continues to serve me well every single day that I'm writing code:

> A function should do _one thing_, and _do it well_.

That bromide's been around _for decades_, but it's lost none of its potency.  Why do we strive so hard to keep our functions _short_???  Because, once your function starts getting too long, it's almost certain that it's no longer doing _one thing_ and _doing it well_.  If it was really doing _one thing_, it would probably be shorter.

Longer functions are almost certainly trying to do too many things.  This makes them prone to bugs.  It makes it hard to write unit tests for them.  It makes it hard for other developers to come behind you and simply _read_ your code to understand what it's doing.  Whenever you're _refactoring_ code, it's almost always a good idea to break a longer function into smaller, _more-targeted_ pieces.

So let's look at a typical component:

```javascript
export default function User(props) {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [age, setAge] = useState('');

  const alertUser = (values) => {
    // create an alert to let the user know that something went wrong
  }

  const checkLogin = (values) => {
    // ensure that the user's logged in and should be seeing this data
  }

  const updatePermission = (values) => {
    // adjust the user's permissions based on some events fired from the return()
  }

  const updateUser = (values) => {
    // do a whole bunch of update functionality here
  }

  return <div>...display a whole bunch of user data here...</div>;
}
```

Maybe this component shell looks pretty logical to you.  We have a series of functions, and depending upon what logic we write inside those functions, it's perfectly feasible that each one is doing _one thing_, and _doing it well_.

But the functional programming fanboys tend to completely gloss over one key fact:

The entire component is itself... _a function_.

This means that we have _one function_ that purports to:

1. Keep track of multiple state values.
1. Display dynamic data relevant to the user.
1. Alert the user to problems.
1. Check the user's login credentials.
1. Update the user's permissions.
1. Update the user's core data.

Wow...

The `User` _function_, in this case, is _definitely not_ doing "one thing" and "doing it well".  The `User` function is responsible for _a wide array_ of functionality.

I can almost _hear_ some of you thinking:

> Well... that's why I wouldn't put all of that functionality _in_ this function.  I'd put those functions _outside_ the component.

Alright... fine.  I already covered above how this really does nothing to make your code "cleaner".  It just flings your functionality off into separate functions (which may actually reside in separate _files_, or even in far-flung _directories_).  But let's just assume for a minute that _your_ component would have all that functionality housed in standalone functions, and that this is, in fact, "better".  

Well, then let's consider another well-worn (and still valuable) chestnut of programming.  It's called the _single responsibility principle_.  It states:

> Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

So now you're yelling, "And _that_ is why I don't use `class`!"

The problem is that you can use _functions_ to define all the features needed in your components.  But the fact is that your _components_ are far more analogous _to classes_ than they are _to functions_.  Just because you avoided that nasty-ol' `class` keyword and you only used "functions", doesn't alter the fact that your _components_ are really working, algorithmically, _as classes_.

Don't believe me?  Scroll back up and read that definition of the _single responsibility principle_ again.  But this time, substitute "class" with "component".  Hmmm... that starts to sound _a lot_ like the principles for writing a good _component_.

Components are pretty cool concepts in React.  A component can:

1. Maintain its own memory.
1. Render its own display.
1. Handle any number of events triggered from actions that are spawned _in that component_.
1. Be cloned.
1. Represent different _states_ (which ultimately spawn different _behavior_) based on initial-or-realtime input.

Now go find some programming friends who _aren't_ JavaScript-only developers.  Rattle off that list of features and ask them what they'd call that "thing".  And then count how many of them say, "I'd call that thing... _a function_."

React's function-based components don't even _sound_ like functions.  Functions - _in almost any other context_ - have a very standard naming convention.  They're named after _actions_.  Specifically, it's usually best-practice to name your functions with a verb and a subject, like this:

* `getUser()`
* `translateText()`
* `validateInput()`
* `callApi()`
* `deleteForm()`
* `filterSearchResults()`

There's not an experienced programmer in the world who's gonna look at function names like these and have any problems with them.  They're clear.  They're descriptive.  And most importantly, they give you an obvious indication of the _one thing_ that the function's designed to do.  

Now let's look at typical names for a function-based component:

* `<AllFormsPage>`
* `<Row>`
* `<TextField>`
* `<UserModule>`
* `<LeftNavigation>`
* `<LoginForm>`

Do any of those component names sound "bad" to you?  Because they sound just fine to me.  But do any of those component names _sound_ like functions to you??  Because they definitely don't sound like functions to me.


## The Verdict

There really is _no_ verdict here.  I've tried to highlight how Hooks-based components can be _good_.  And how they can be... suboptimal.  Similarly, class-based components can be _good_.  And they can _also_ be... suboptimal.

I've been writing _a ton_ of Hooks-based components lately.  And you know what??  I can honestly say that I _like_ them.  I'm going to continue writing more of them.  They have definite shortcomings.  But some of their positives are very alluring.  

I have no doubt that _you_ probably have very strong feelings for-or-against Hooks, and for-or-against classes...