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:
// 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:
// 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:
// 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:
// 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.
Top comments (15)
I agree for a big part with your article. People are always using the next big thing, but that doesn’t mean it is better than the previous thing.
However, for me, hooks provide a new way to reuse specific logic. I can simply import ‘useAnything‘ and it works. Whereas with classes this isn’t possible due to different lifecycle methods. Hooks allow me to split code by functionality, not lifecycle. With classes you would need HOCs or render props to achieve the same.
So no hooks are not always better, or more readable than classes. But, for me, often they are. Luckily both are here to stay.
Good points. Hooks certainly have some advantages. I'll explore those a little further in a future post.
You can say that again.
Another point I would like to add is when the community wholeheartedly moves towards the new thing, the momentum it creates forces the people sitting on the fence to follow the herd.
The latest and chic technique gets the majority mindshare. All new libraries get developed using the new thing. Old libraries that used past practices fall into disuse and stop getting the updates and fixes.
Modern web applications rely on so many libraries that size of node_modules is a running joke. It is turtles all the down. So for better or for worse, one has to follow the latest trends.
I would like to add a few points:
4) I feel entirely confident that I can answer that for you. In fact, it was kinda the underlying point from this post. I'm not even joking or exaggerating when I say I'm convinced that the Redux / Hooks / Abramov team is front-and-center in the JS elitist war against that ugly, nasty, evil, unconscionable
class
keyword. Seriously. I'm pretty sure that they just hate the idea that they might have to useclass
in their own code, or see it in anyone else's code.I'm not claiming that Hooks are a one-to-one port of class-to-function, but they're pretty close. I truly believe that the underlying motivation for the creation of Hooks was a deep desire to "exterminate" that horrible
class
keyword from the code. And I'm equally confident that the Hooks team would rather saw off their own leg than have to extend any Hooks-based features back into class components.IMHO, one subtle sign of this is with
create-react-app
. I use that package frequently whenever I need to spin up a quick, local, proof-of-concept app. It's quick and easy and convenient.But I started noticing a few years ago that
create-react-app
's base install changed in a clever little way. Now, when you spin up a new project with it, the defaultApp.js
component is created as a functional component - whereas, in the past, it was always created as a class-based component.Of course, if you still want to use class-based components, it takes all of 30 seconds to change it. And even if you leave the default
App.js
component as functional, that doesn't stop you from writing new components in the project that are class-based. But I felt that this subtle shift was telling. It was like having Abramov standing behind you and whispering into your ear, "Don't use those rotten no-good class-based components."1) That's so funny cuz I was literally laying in bed last night pondering this same issue - but from a more theoretical standpoint. I've had it drilled into me from my earliest days as a programmer that:
It's one of the simplest, and most powerful, concepts for writing better code. When reviewing code with other senior devs, you'll almost never get any argument about: "I broke this code up into separate functions that each do a specific piece of the work."
But functional components (especially those with Hooks) are the antithesis of this incredibly basic programming concept. JS purists start to get an epileptic fit whenever they see that dirty nasty 'class' keyword in someone's code. They start climbing up on their functional-programming lectern. They start to sound as though they alone are responsible for the holy sanctity of the language.
Then they create a functional component that itself contains half a dozen other functions. And it uses state. And context. And handles display. And it provides post-rendering with
useEffect
.And... this doesn't seem to bother any of the "purists" who are so affronted by the mere sight of the
class
keyword.I agree about some hooks being much nicer than others and feel natural while some feel straight out forced (probably in the name of replacing classes completely as you said). I think it is fair to call the original functional components pure functions. The get props and input and emit jsx (always the same jsx given the same props). You could, in theory, do side effects inside (like making an ajax call) but that would result in terrible performance and wasn't the intent.
3) I'm sure I'm gonna delve into this in some future blog post.
Hooks with
useState()
? Sure. I love it. It's simple. Easy to manage. Intuitive. Give me more.Hooks with
useContext()
? Oh man, this is one of the strongest features of Hooks. So much cleaner and easier than doing it in class-based components - especially if you're consuming multiple contexts in one component.Hooks with
useEffect()
and/oruseCallback()
and dependency arrays???Hold your nose. Some of the stuff I've seen with
useEffect()
feels to me like a code smell - a genuine Grade-A fart, baked up and delivered to your nostrils courtesy of Dan Abramov himself.Don't hate on Dan. My impression is that he is one of the "good guys" even though I am far from being a fan of Redux or some of the hooks.
Here are two posts of his I think might make you see him in a different light:
overreacted.io/goodbye-clean-code/
medium.com/@dan_abramov/you-might-...
Haha - you know what?? You're absolutely right! I know that in my posts, and in some of my responses, I definitely come across as some kind of "Dan Abramov Hater". And that's my fault. I should probably tone that down a bit. Cuz none of my (cranky, heavily-biased) opinions have got a damn thing to do with Abramov. And I'm certain that my tone, at times, undermines my underlying message.
To be absolutely fair, I can't point to a single quote from him, or a single post from him, or anything else directly from him, that ever made me think, "This guy's a douchebag." I can't even point to anything from him that would make me think, "This guy's professional ideas are misguided." It's not faux-humility to state that Abramov has probably forgotten more about JS than I've ever learned.
This is not an excuse (cuz again, I should really tone down the anti-Abramov rhetoric), but I just get really frustrated sometimes with the direction that I see some things going in. And then I look at the person who's perceived as the driver of those directions, and... I start typing angry shit on my keyboard.
More to the point, I get annoyed with the fanboys who follow these "thought leaders" and then pick-and-choose their own misconstrued bits of faux logic to back up their trolling. An example is the "class-based components are being phased out" #FakeNews. Abramov has never said that. The documentation on the React/Hooks site directly refutes that. But the fanboys latch onto the latest thing that was released by Their Lord And Savior, and then they start twisting the narrative to "bolster" their own immature code-shaming.
But that's got nothing to do with Dan, does it? And even as some guy who's cranking out his silly little blogs, I should be a bit more careful (and respectful) with what I write.
Good call-out!
Spot on. And is there is anything in computer science that lends itself more to objects than UIs? Has anyone ever looked at a piece of a UI and though, "Nice function!"? Nope.
Bingo! That's a big reason why React originally appealed to me - because, when crafting a UI, you have all these... "things" on the screen. Those things have actions (events) that can be triggered based on user input. Those things have their own bits of logic (functions) that should happen at specified times, or in response to specified input. Those things have their own internal memory (state). And they have their own rules about how-and-where to generate display (the
render()
).Those things... are called objects.
Well not exactly.... They're called components, not objects.
React has always been based on functional programming concepts, even before hooks, e.g. pure functions, composability, immutability, Redux, etc. Hooks are just the next logical step to making the library more FP-oriented.
I personally love hooks as I find it much faster and easier to write components than with classes (there's also a slight performance boost). Maybe you'd prefer a more OOP-oriented frontend framework like Angular?
I'm talking about paradigms here. No one with even an elementary understanding of JS thinks for a moment that
JS_Objects === OO_Objects
.Find programmers who aren't long-time JS/React heads. Then tell them, "I have this thing. It can maintain its own internal memory. It has a view, through which display output is directed. It can have any number of pre-defined actions/events, based on user input, or based on conditions that are passed into it. It has a defined life-cycle. It probably has ancestors. And it can have descendants. It can exist as a single entity, or it can be cloned. The clones can be perfect copies or they can behave differently, based on the initial parameters. So... with all of those parameters in mind, what would you call this thing??"
Then sit back and count the number of people who say, "I'd call that thing a function"...
We all understand that this stuff ultimately gets transpiled down to functions (whether we start with a function or a "class"). But I (and the original commenter above) are talking about the paradigm that best fits a UI "object". (Or "component". Or "element". Or whatever particular name we want to call it.) The paradigm I'm referring to is the exact same paradigm that was directly fostered by the React framework - for years.
As for Angular, I've done a lot of Angular. I can work in it just fine - but I prefer not to. I don't particularly care for it.
Hmmm fair enough. Thanks for the explanation.
In terms of what's "easier" to reason about in React/JS, I personally find functional components easier to understand than class components. E.g. the other day I had to go through a couple of React libraries using classes. It took me a while to adjust to understanding what "this" referred to. Maybe I'm strange for legitimately disliking class components 😆