DEV Community

Cover image for A Hooks-vs-Classes Report Card

A Hooks-vs-Classes Report Card

Adam Nathaniel Davis on April 11, 2020

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 ...
Collapse
 
jarodpeachey profile image
Jarod Peachey

Great article! In my opinion, hooks are great because of the shared state and useContext() hook. However, I've spent countless hours trying to recreate lifecycle components, which I could have done in 10 minutes with a class-based component. useEffect() is a pain to work with.

Ultimately, I use global state more often than I need a specific lifecycle, so I go with hooks more often, but I can see why one would choose classes.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

hooks are great because of the shared state and useContext() hook

Absolutely. This is probably the primary reason why I've switched most/all of my new dev to Hooks. They have some definite... drawbacks. But the positives can also be powerful.

I've spent countless hours trying to recreate lifecycle components, which I could have done in 10 minutes with a class-based component. useEffect() is a pain to work with.

Amen. Amen. And, oh... did I mention?? Amen.

Collapse
 
wejdi_gh profile image
Wejdi

You can easily use Redux for state management ...

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

I could also use a sandblaster to wash my hands...

Thread Thread
 
wejdi_gh profile image
Wejdi

hahaha very funny :) it depends on how complex your application is going to be . Context is for low-frequency updates and simple Alpplication , but Redux is the most powerful solution for complex application state .

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

I understand that if you're a "Redux guy" then you're probably gonna continue to be a "Redux guy" - probably for a very long time. But with the improvements to the Context API and the ability to use shared Hooks, I don't honestly understand why anyone would ever want to inject the megalithic monstrosity that is Redux into a new project. I've written several articles highlighting my not-so-subtle feelings about Redux.

Thread Thread
 
wejdi_gh profile image
Wejdi

I understand also that if you're "NOT" a "Redux guy" then you're probably gonna continue to be "NOT" a "Redux guy" - probably for ever :)
Peace .

Collapse
 
sirseanofloxley profile image
Sean Allin Newell

I like what you've written here; I'd like to contest your assertion around the SRP and the 'golden function rule' though - and then I'll agree with your assertion too because I'm double minded like that πŸ˜….

I've written some F# code, specifically with the Suave web framework. In that framework, it's idiomatic to use functions to compose a type called a WebPart to build up functionality of your app. The 'root' webpart would technically be the entire API/Webserver which is certainly 'not a pure function' that does one thing well. However, isn't main 'just' a function too? main (or index) can be written well by delegating and composing the app as it should do since its the conposition root, or
poorly if main triess to do far too much all at the top level.

So at some level, there are higher order functions whose responsibility is to compose smaller, purer functions.

Can't the same be said of react's functional components? Each component delegates to hooks and sub components. This is analgous to the web part type in my mind, and indeed static void main and composition roots.

That being said, your point is still very valid. Without really good names, coding discipline, or true functional programming and coherence (like Elm/PureScript/sub-set of F#), functions in JS can just become big fat classes. Heck, what is the responsibility of those IIFEs we had to write 'back in the day' that our bundlers are writing for us now?

Great food for thought! I'm gonna have to see if I can't name my components better to reflect their reaponsibilities.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited

Excellent feedback! And I totally agree with everything you've written.

My point isn't to say, "Functional components don't sound like functions - so you shouldn't use functional components." My point is just to illustrate the contradictions when a Functional Programming Acolyte (and Certified Class-Hater) tries to yell you down because those horrible, nasty, unconscionable class-based components don't comply with their "pure" vision for functional programming in JavaScript.

Even in the best OO codebases, there are some classes that look-and-feel a whole lot like... functions. (static void main is an excellent example of this.) And even in the best FP codebases, there are some functions that look-and-feel a whole lot like... classes.

When we come across those scenarios, I think it's absolutely healthy to run the "coding bromides" through our head and say, "Hmm... Have I really architected this application properly??" But I know full-well that, sometimes, as an experienced developer, you will run that "check" and come to the conclusion that, "Yes... This really is the proper way to go about it."

One of the first steps in learning to program is to learn "the rules" - whatever those "rules" may be for the particular flavor of tech in which you're building solutions. The "rules" are there for a purpose - and generally speaking, they're usually quite helpful.

But the process of becoming a well-experienced programmer also entails learning when it's appropriate to break those "rules". For example, I've written-and-deployed individual functions that contained hundreds of lines of code. Normally, that's a "code smell". But there are absolutely some times when it's 100% appropriate.

This often comes to mind when I'm writing/demonstrating a React class-based component and some kid (sorry if that sounds dismissive - but it's often quite TRUE) says to me, "Dooood... You shouldn't be writing classes in JavaScript." And I gotta admit that, in those scenarios, my inner (snarky-as-hell) "developer voice" is thinking, "Yeah... How bout you just stay in your lane, kid???"

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

...and then I'll agree with your assertion too because I'm double minded like that

I also wanted to harp on this (excellent) statement from you, because I believe it highlights a broader point that I'm trying to make in this post. (And indeed, I think this point permeates many of my other posts as well.)

Part of being a programmer is the mindset that a Given Set of Inputs/Logic X should always (or, ideally) result in a Given Output Y. In functional programming terms, we call this a pure function. Taken to a greater extreme, we tend to think that everything can ultimately be coalesced down to a Boolean.

I'm not "angry" about this mindset. I have it. Nearly every solid developer I've met also has it (at least, to some degree). But this mindset also leads to potentially harmful dictates in the development community.

Some "thought leader" comes to believe that a particular programming pattern is "bad" or "harmful" or a "code smell". They start writing think pieces about it. They start proselytizing about all the reasons why Pattern A is "wrong" and Pattern B is "right". Before you know it, people are writing additional libraries with the underlying intent of banishing Pattern A.

But everything in programming (or, in life) isn't always as simple as classes === FALSE or functions === TRUE. You can hold two thoughts. And if you're holding those thoughts, it doesn't necessarily mean that this is a problem to be solved. It's entirely possible that classes might, sometimes, be TRUE. And functions might, sometimes, be FALSE.

So... even though your lighthearted acknowledgment may seem (to some) like a throwaway comment, I think it illustrates the deeper knowledge that experienced devs can bring to the table when we decide to wade into the debates about whether Pattern X is "good" or "bad".

Collapse
 
gregfletcher profile image
Greg Fletcher

Thanks for the article. I enjoy reading your POV!

I fall on the other side of the discussion concerning functions (Hooks) vs classes in React, but I've got nothing against classes. I use them in other projects but not React since the Hooks API (IMO) is much simpler than the classes one. Sure, developers sometimes still make messy code (me) that's hard to read but I believe that Hooks give you a better head start to make things simpler. Plus, packaging up state management in a custom hook for reuse is a great API that I use all the time.

Thanks again for the article. I appreciate the effort you put into it. It's super important to have these discussions and I look forward to more.
Cheers!

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Thanks for the feedback! I'm genuinely curious about this statement:

Plus, packaging up state management in a custom hook for reuse is a great API that I use all the time.

How, precisely, have you chosen to do that? I'm asking because I wrote an article highlighting a particular technique that I've discovered (dev.to/bytebodger/hacking-react-ho...). But I've also found a lot of misinformation around this topic. When people make statements like this, it often sounds (to me) like you can just create a custom Hook that uses its own state, and then you can just use that custom Hook wherever you want, throughout the app, to share that state - but... it doesn't work that way. And I rarely hear people talking about the specifics of how to make that work.

So I'm genuinely curious to see the approach you've taken.

Collapse
 
gregfletcher profile image
Greg Fletcher

Good question. I'll do my best to answer below.

It's key to note that Hooks do not solve Global state management. They're just a different API for state management in general, whether that be local or global state. If you want, you can use context, prop drilling, etc for your global state management. But Hooks were not made specifically for the global state problem, just state.

So what do I mean by packaging up state management for reuse?

I can (potentially) reuse that custom hook in another project without changing the code. I say potentially because it depends on how well the hook was written in order to make it reusable in another project.

import useFetch from './hooks/useFetch.js'

function SomeComponent() {
  const data = useFetch("someurl/get/me/data");

  if (data) {
    return <NeedData data={data} />;
  } else {
    return <Loader />;
  }
}

This has two potential benefits.

  1. I can extract state management, logic, whatever else you might be doing, and put it into a custom hook and now my state is no longer clogging up my component. Less lines of code for me to read when scanning over it. This is a big deal for me because it helps me understand the flow of the component that I'm making.
  2. I can also reuse the same custom hook in the same app or separate projects. In this case, we are not sharing state, only the functionality. To me, this is a big win because we are closely following the DRY principle.

As far as sharing global state deeply into your app , that's still the same as ever. Either use prop drilling, context, or some library like Redux.

By no means am I saying that Hooks are perfect, but IMO they're a step in the right direction.

As far as rendering state deeply in your hacking-react-hooks example you could use the children API.

function TopLevel() {
  const { increment, decrement, reset, invert } = useCount();

  return (
    <div>
      <MainApp>
        <Header />
        <CountingPage>
          <Larry increment={increment} />
          <Curly decrement={decrement} />
          <CurlyJr invert={invert} />
          <Moe reset={reset} />
          <Text />
        </CountingPage>
        <Footer />
      </MainApp>
    </div>
  );
}
function MainApp({ children }) {
  return <div>{children}</div>;
}
function CountingPage({ children }) {
  return <div>{children}</div>;
}
Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis • Edited

Excellent response - and sincerely appreciated! When I look at the example you've given with the children API, it strikes me as "I'd do this with Context". This is not to imply that your example is inferior or that mine is "better". Just different paths to the same result.

In some ways, your response has also further-solidified what is (IMHO) a central "issue" with Hooks. Or maybe it's just an "issue" with JS devs. I don't know.

But the "issue" I'm talking about is that, when I've heard people describing Hooks, I've become attuned to the concept that they're almost speaking a different language. A "Hooks person" will often say "Oh, I use Hooks to share state" and "non-Hooks person" will hear that and have an entirely different idea about what the "Hooks-person" was trying to communicate. Then the "non-Hooks person" sits down and starts playing with Hooks - and the constructs don't seem to support everything they were hearing from the "Hooks-people".

To be absolutely, 100%, crystal clear on this, I am NOT saying that this is some kind of "fault" with Hooks, or with "Hooks people", or with React in general. It just seems to be, IMHO, a strange dichotomy that has been building in the React community. Neither side is "right" or "wrong". But it still feels to me like... a problem. And I have no idea what (if anything) can be done about it.

This is what I was trying to get to in the "Developer Cognition" part of the article. I don't honestly think that either "side" is right or wrong. It just feels, increasingly, like there are two different... dialects being spoken.

All that being said, I think the examples are excellent and they've given me some good "food for thought". I appreciate the time!

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited

You can't convince functional programmers to use classes and old programmer to use hooks, everyone should use what they want!

I couldn't agree more. That was the main concept under the "Developer Cognition" section. And that's definitely part of my inspiration to write an article like this. I don't like any environment (which is all-too-common), where they have made some universal dictate like Thou Shalt Not Use Classes or Thou Shalt Not Use Hooks. Unfortunately, devs often get polarized on such issues.

I only ask myself how you deal with UI Libs like Material or Graphql Clients like Apollo who are going in hooks only direction, and likely all libs will go that way, do you still write Class Components then?

Yeah... I kinda referenced this in the "Legacy Compatibility" section. I haven't (yet) had any real problems with Material UI (and I use it all the time). I think this is because Material UI gives you a bunch of self-encapsulated components and then allows you to drop them into your own components - whether those components are functional- or class-based. When it's done like that, the functional/Hooks components really play quite nicely with classes.

It's kinda like TypeScript. I'm not using TypeScript in my dev. But many of the packages that I use are written in TypeScript. And for the most part... I don't care. Because all of their behind-the-scenes TypeScript doesn't affect how I use the package in the middle of my components.

I have, however, run into the exact problem you reference with GraphQL. In the latest/greatest Apollo library, they want you to reference the Hooks directly as a means to use the package. In those cases, you either convert your classes to functions (yech...) or you don't use the library at all. (Or you write some horrific, bloated wrapper function that allows you to leverage the Hooks from inside a class - also... yech.) I'll freely admit that this annoys the crap outta me.

I actually ended up scrapping Apollo altogether. But not exactly because of the "Hooks problem" (you can get versions of Apollo that play along just fine with classes). My "beef" with Apollo (and other GraphQL libraries I tried), is that they want you to hand control over the API calls to the render cycle - which I find to be completely untenable.

I wrote a post that references this broader issue here:

dev.to/bytebodger/react-s-odd-obse...

I ended up dropping all GraphQL libraries and I just wrote/formatted the GraphQL queries manually in my API components. Afterall, GraphQL is nothing more than a specific way of formatting a query (i.e., a string). And I realized that I didn't really need an additional library for that.

Would it not be easier for you to stop using React and maybe look in some new things like Blazor?

Yes and no. Sometimes I write some... "cantankerous"-sounding shit. But it doesn't mean that I've soured on React as a whole. In fact - I still love it. I just get frustrated sometimes by some of the forces that are driving it in suboptimal directions.

And I'm not a "Hooks hater". In fact, I've kinda switched most of my new dev to Hooks. Yes, they have problems. But they also have some really strong benefits. So it's a give-and-take.

Also, when it comes to new libraries/frameworks/paradigms/etc., like most devs, I'm always looking at other alternatives with genuine curiosity. But I also really enjoy the process of getting a paycheck.

It's one thing to say, "Oh, man! I just found this brand new library/framework/paradigm/etc., and it's friggin awesome!!" It's an entirely different thing to take that back to your team (or your company) and say, "Here's why I think we should totally abandon all that React stuff we've been doing and adopt this Hot New Thing."

Collapse
 
tonivj5 profile image
Toni Villena

Good series! I'm really enjoying your POV πŸ‘€πŸ˜†

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Thank you!