DEV Community

loading...
Cover image for A Hooks-vs-Classes Report Card

A Hooks-vs-Classes Report Card

Adam Nathaniel Davis
React acolyte, jack-of-all-(programming)trades, full-stack developer
・Updated on ・15 min read

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:

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

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:

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

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:

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

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.

  2. 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:

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

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.
  2. Display dynamic data relevant to the user.
  3. Alert the user to problems.
  4. Check the user's login credentials.
  5. Update the user's permissions.
  6. 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.
  2. Render its own display.
  3. Handle any number of events triggered from actions that are spawned in that component.
  4. Be cloned.
  5. 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...

Discussion (20)

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 Author

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 Author

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 Author

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

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

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

Thank you!