Reinventing the Wheel With React Hooks
Tags: react, hooks, javascript, oop
Published: 2020-04-04 06:06:11

This isn't your typical piece of Hooks brainwashing, designed to shame you for using class-based components, and extolling the Ever-So-Holy Virtues of Hooks.  This isn't even a deep-dive into what Hooks are and why you should (or shouldn't) use them.  Instead, this is a case study in how the dogma of The Holy Order of JavaScript has led to an extended exercise in dumping one perfectly-useful paradigm in favor of the new Flavor of the Day - merely because certain "thought leaders" decided to go on a Holy War against JavaScript's `class` keyword. 

## Background

I've been doing professional React development now for about 5 years (and many other flavors of development for more than 20).  I don't easily jump onto new technologies.  I have too much "real stuff" to accomplish to be chasing every  "Package of the Day" that pops up on NPM.  So I wasn't a bleeding-edge adopter of React.  But when I did finally "see the light", it definitely _clicked_ with me in a significant way.  

As a long-time JavaScript dev, I watched (with some amusement) as a certain cadre of the language's "thought leaders" started railing against the unfathomable, unholy, unimaginable horror that is: the JavaScript `class` keyword.  I read many of the same think pieces that you probably did.  I read all of the "justifications" for why it was supposedly-evil - even though it's nothing more than **_syntactic sugar, that provides absolutely nothing to JavaScript that you couldn't already do_**.

I can't say that I really cared too much about the circular debates.  I thought I was fairly "class-neutral".  I saw `class` for what it was - simply a _keyword_.  I understood that keywords are neither "good" nor "bad".  They just... are.  If you want to use that particular keyword, then great!  If you don't like that keyword, that's great, too!  _You do you!_

Meanwhile, as a React dev, I couldn't really escape `class`.  Before 2018, if you were cranking out React code, you were mostly doing it with _class-based components_.  

Sure, there's always been a conceptual focus on _pure functions_.  But pure functions have no _life cycle_.  They have no _state_.  They're... functions.  And if you're building any kinda sizable app, at some point, you're gonna have to reach for those life-cycle methods and those state-management capabilities.

## The Cost of Dogma

Every flavor of tech has its _dictators_.  The snobby types who will tell you that your code sucks if you ever make the mistake of using an old-skool `function()` declaration instead of an oh-so-cool _arrow function_.  They try to code-shame you because you don't leave an empty line above your `return` statements.  Or because you don't put the opening `{` on its own line.  

For the most part... I just ignore these dictators.  I have deadlines.  I have paying clients.  I can't be bothered to refactor a 100k+ LoC application because some hot new blog post says that there should be _no inline styles_.  Write your snooty blog posts.  High-five your dictator friends.  I have work to do.

But that changed in 2018.  In October of that year we were blessed from on-high with... _Hooks_. 

> And the fanboys rejoiced.

When Hooks were introduced, I worked with an extremely talented React dev and he was almost beside himself.  He was _gleeful_.  And I was... happy(?) for him.  But he kept showing me these Hooks examples and praising them as so obviously-superior.  And I kept looking at the examples and thinking, "Yeah... it's just another way to do all the stuff we could _already_ do - in classes."

You see, it's fine to tell all your buddies that tabs are wayyyyyy superior to spaces.  But when you have the clout for your new package to be included right alongside React's core build - and your new package attempts to _enforce_ tabs, or your "community" attempts to _code shame_ people into using tabs, well then... you're just kinda being a jerk.

## Lessons From Marketing

Not that any of this really bothered me much at the time.  I still had thousands of lines of code to work on - code that was humming along perfectly in class-based components.  No one was gonna pay me to rewrite all of their perfectly bug-free class-based components.  

If you've ever taken a Marketing 101 course, you learn that people need a _compelling reason_ to change products.  Just telling me that you have a new toothpaste won't compel me to switch to it.  Or even _try_ it.  I already have a prefered brand of toothpaste.  It works great.  It tastes great.  

If you want me to _switch_ to your new brand of toothpaste, you'll have to come up with something better than "It's new!" or "It's different!"  You'll have to give me a _compelling reason_ to change.

Sadly, these "market forces" tend to get perverted in the programming community.  Joe comes up with a New Way to write JavaScript.  He yells to all of his buddies that they should code in the New Way.  And... everyone just shrugs.

But what if Joe is seen as a "thought leader"?  What if he's already hailed by the fanboys as a programming "legend"??  Well... in that case, the fanboys start lining up behind him.  Not only do the fanboys start shifting all of their coding to mirror Joe the Thought Leader, but they also start _code-shaming_ you if you don't fall inline.  

Don't believe me?  Consider a comment that was placed on one of my other posts that had nothing to do with the class-vs-function debate:

> I will never understand how anyone can prefer class over function component.

That's it. No intelligent discourse on the _content_ of the post.  No meaningful feedback at all.  Just a trolling non sequitur because my stooopid code examples used... classes.

The fanboys aren't content with simple trolling.  They're also happy to peddle in Grade-A #FakeNews.  In numerous examples all over the web, I've seen almost this exact same comment left on React blog posts:

> Class-based components are being phased out.

Umm... no.  They're not.  If you don't believe me, just spend a few minutes _reading the Hooks documentation_ on the core React site.  It's clear.  It's unambiguous.  It states that, **"There are no plans to remove classes from React."**  Apparently, the fanboys are unwilling (or unable) to read this basic unequivocal statement direct from the React team.

## Comparisons

For the last couple of years, I've been mostly silent about Hooks.  I don't hate them.  I don't love them.  I just see them as... a thing.  A _tool_ that can prove useful in certain circumstances - and not-so-useful in others.  Like almost _any_ React dev, I've tinkered with them in my local environment.  But for the most part, they've been a side note.  This happened because my employers - the people who actually, you know, _pay me_ to write code, still have huge legacy codebases that are filled with classes.  And it's not exactly straight-forward to start converting all that stuff to Hooks.

The last couple months have been a big eye-opener for me.  I joined a new company and we have the privilege of doing some "green fields" development.  Before we wrote anything, we all had a discussion about tools, techniques, and best practices for the new project.  And we decided as a group that all this new code would be done with pure functions and function-based components (i.e., with Hooks).  So I've finally had the chance to do a true "deep dive" on Hooks.

Not only have we been cranking out brand new code with Hooks, but I really wanted to get up-to-speed quickly on them.  I have a large side-project that currently sits at more than 30k LoC, and I've taken it upon myself to convert _all_ of that to Hooks.  After spending several hundred hours deeply immersed in all-things-Hooks, I can confidently say that my assessment is...

> Meh...

Before you start rolling your eyes, please understand that I have nothing in particular _against_ Hooks.  They're fine.  They're great.  But when you've converted a few hundred class-based components into Hooks, after awhile it's amazing how much the new, oh-so-cool, function-based components look like... class-based components.

First, let's look at a dead-simple example:

```javascript
// the old, evil, class-based component
export default class CancelButton extends React.Component {
   render() {
      return (
         <Button
            onClick={this.props.onClick}
            style={{
               backgroundColor : the.color.cancel, 
               color : the.color.white.text, 
               ...this.props.buttonStyle,
            }}
            variant={the.variant.raised}
         >
            <FontAwesome
               name={the.icon.x}
               style={{marginRight : 10}}
            />
            <TranslatedTextSpan english={'Cancel'}/>
         </Button>
      );
   }
}
```

Is now this:

```javascript
// the amazing, fantabulous, function-based component
export default function CancelButton(props) {
   return (
      <Button
         onClick={props.onClick}
         style={{
            backgroundColor : val.colors.lightGrey,
            color : val.colors.nearWhite,
            ...props.buttonStyle,
         }}
         variant={'contained'}
      >
         <FontAwesome
            name={val.icons.x}
            style={{marginRight : 10}}
         />
         <TranslatedTextSpan english={'Cancel'}/>
      </Button>
   );
};
```
Wow... what a difference!  The function-based component is just _sooooo_ much better, _right_??? 

Umm...

OK, to be fair, maybe that example is just too simple to illustrate the many benefits of function-based components.  After all, it doesn't even have any Hooks in it.  So let's look at something a bit juicier:

```javascript
// the old, evil, class-based component
export default class LoginForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      fields : {emailAddress : I.getDefaultFieldProperties()},
      okButtonIsDisabled : true,
    };
  }
  
  checkForEnter(event) {
    if (!this.state.okButtonIsDisabled && event.keyCode === the.keyCode.enter) { this.callCreateLogIn(); }
  }
  
  componentDidUpdate(prevProps, prevState, snapshot) {
    this.updateOkButtonState();
  }
  
  dismissAlertAndGoHome() {
    app.DisplayLayer.dismissAlert();
    app.DisplayLayer.updateModule(<HomeModule />);
  }
  
  goToRegister() {
    app.DisplayLayer.updateModule(<RegisterModule />);
  }
  
  render() {
    const {fields, okButtonIsDisabled} = this.state;
    return (
      <FullHeightPaper>
        {/* render ALL THE THINGS */}
      </FullHeightPaper>
    );
  }
  
  updateFieldState(event) {
    const updatedFieldState = I.getUpdatedFieldState(event.target, this.state);
    this.setState(updatedFieldState);
  }
  
  updateOkButtonState() {
    const {fields, okButtonIsDisabled} = this.state;
    if (this.logInFormIsInFlight) { return; }
    const someFieldsAreInvalid = Object.keys(fields).some(fieldName => fields[fieldName].isValid === false);
    if (someFieldsAreInvalid !== okButtonIsDisabled) { this.setState({okButtonIsDisabled : someFieldsAreInvalid}); }
  }
}
```

Is now this:

```javascript
// the amazing, fantabulous, function-based component
export default function LoginForm() {
   const displayLayer = useContext(DisplayLayerContext);
   const model = useContext(ModelsContext);
   const sessionApi = useContext(SessionApiContext);
   const [emailAddressField, setEmailAddressField] = useState(model.textField());
   const [okButtonIsDisabled, setOkButtonIsDisabled] = useState(true);
   
   const checkForEnter = (event = {}) => {
      if (!is.aPopulatedObject(event))
         return;
      if (!okButtonIsDisabled && event.keyCode === val.keyCodes.enter)
         sendLogIn();
   };

   const goToHome = () => {
      displayLayer.updateModule('home');
   };
   
   const goToRegister = () => displayLayer.updateModule('register');
   
   const handleErrors = (errors = []) => {
      if (!is.aPopulatedArray(errors))
         return;
      if (errors.find(responseError => responseError === 'email does not exist')) {
         let alert = model.alert();
         alert.icon = 'warning';
         alert.text = translate('The email address supplied could not be found in our records.');
         alert.title = translate('Oops!');
         createAlert(alert);
      } else {
         displayLayer.createGenericErrorAlert();
      }
      setEmailAddressField(model.textField());
   };
   
   const updateFieldState = (event = {}) => {
      if (!is.aPopulatedObject(event))
         return;
      let clonedEmailAddressField = cloneObject(emailAddressField);
      clonedEmailAddressField.value = event.currentTarget.value.trim();
      clonedEmailAddressField.isValid = isEmailAddressValid(event.currentTarget);
      setEmailAddressField(clonedEmailAddressField);
      setOkButtonIsDisabled(!clonedEmailAddressField.isValid);
   };

   return (
      <FullHeightPaper>
         {/* render ALL THE THINGS*/}
      </FullHeightPaper>
   );
};
```
OK, this is a _much_ more "involved" example.  We're using `useState()`!  And we're using `useContext()`!  And the function-based component is a clear, hands-down "winner" over the class-based component... _right_???

Umm...

If you're not instantly recognizing the _clear and obvious_ superiority of my function-based component over my old, ugly, nasty, class-based component... then congratulate yourself.  You're not a mindless fanboy who's singing the Hooks praises merely because one of React's main contributors _told you to_.

## Real-World Code

I've seen soooo many lame examples on the web where someone converts an old, ugly, class-based component into some (supposedly) beautiful function-based component and then uses it to sing the praises of Hooks.  The problem with these examples is that they are rarely reflective of _real, live, out-in-the-wild_ code.  

To be perfectly clear, I can absolutely find _some_ examples where my function-based component ended up somewhat smaller and nominally "cleaner" than the original class-based example.  Unfortunately, I've found these examples to be relatively rare.

When you really start diving into Hooks, the reasons for the near-one-to-one conversion become clear:

> Hooks are just another way to do _what you could already do_ in class-based components.

State's messy.  But you can rarely avoid state management altogether.  So when you start porting all that state management from class-based components over into Hooks, it looks shockingly similar.  

Life cycle's messy.  But you can rarely avoid life-cycle management altogether.  So when you start porting all that life-cycle management from class-based components over into Hooks, it looks shockingly similar.

And I haven't even shown any of my conversions that use `useEffect()` and `useCallback()`.  When you start getting into that level of detail, it's not uncommon for the class-based components to look downright _simpler_.

## The End-Result of Dogma

Let me tell you _exactly_ how we got to Hooks.  About 5 years ago, a certain segment of the JavaScript Illuminati decided that:

> Classes are bad... mmmkay???

When they did, this presented a quandary for the React community.  React was already well down the `class` road.  And even though the React community started to yell ever-louder about the horrible, unsightly, ugliness of that unconscionable `class` keyword, there was always a central problem:  You couldn't do a lot of "React stuff" in pure functions.  Specifically, you couldn't do some of the key features like state and life-cycle management.

The whole `class` hatred might've died right there, _except_... The Redux team was totally onboard with the "classes must go" mantra.  So they created Hooks.  Then they used their considerable clout in the community to make it clear that Hooks absolutely are **The Next Big Thing**.

> And then... the fanboys mindlessly fell inline.

So now, if you're trying to write a React blog post, or demonstrate in an interview, some concept _that has nothing to do with the classes-vs-functions debate_, you must be wary of any potential Class Haters lurking in the audience.  Because if you throw one of those evil `class` keywords on the whiteboard, that might literally be the end of the discussion for them.

## Drop the Hatred

You might think that I'm some hardcore Hooks hater.  But nothing could be further from the truth.  The simple fact is that Hooks are a _tool_ in your tool belt.  Your hammer isn't "good" or "bad".  It's "good" in certain situations.  And downright pointless in others.  The same can be said about Hooks.  _Or classes._  

> Code-shaming someone because they use classes makes as much sense as vilifying a craftsman because he uses a hammer.

I've actually _enjoyed_ a lot of my recent Hooks development.  They have _some_ clear advantages (that I'll highlight in a future post).  I've also found that they definitely have some... _challenges_.  Challenges that I didn't have to deal with in class-based components.  

The key isn't to decide whether Hooks are "bad" and classes are "good" (or vice versa).  The key is to understand what Hooks and classes are: _syntax_.