Ever since the introduction of Hooks, questions have naturally lingered about
- Should I use Hooks?
- Should I convert my existing codebase to Hooks?
- All the cool kids are using Hooks. I wanna be cool... right??
In the prior articles in this series, I've already done a lot of pontificating about Hooks: What's good about them. What sucks about them. And everything in between.
This particular post assumes:
You've been diving in with Hooks and you either A) love them, or B) want to steer more of your dev to them because they kinda feel like "the future" of React.
You're not working on a "green fields" project where you can make all the high-minded, theoretical choices about how the project will be built. You're staring at a ton of class-based components. And you're wondering when/if you should try to tackle converting the legacy components to Hooks.
So what do you do with all of those mean, ugly, nasty class-based components that are sitting in your otherwise-pristine React codebase? Well, you're in luck.
Over the last several months, I've been writing many thousands of lines of new Hooks-based components. I've also been converting hundreds of class-based components into Hooks. After all of this intensive effort, I feel that I've gained some unique insight on this matter, and I can now offer you a measured, strategic, step-by-step guide that explains exactly how and when you should convert your classes to Hooks.
Put your hands up and slowly walk away from the keyboard. Go have a beer. Chat with some friends. Binge-watch Tiger King, for the third time, while you're waiting out the quarantine. No matter what you actually choose to do, you are, by no means, to actually start converting any of your React classes to Hooks.
Sound simple? It is. It's a gentle way to "ease into" the classes-to-Hooks migration process. The real work won't begin until we get to Step #2.
GOTOStep #1. Repeat this entire process, as many times as necessary, until the whole "I must convert the class-based components" bug finally clears your system. Don't be ashamed to call for help. You might need some non-Hooks pals to come over and take you out for a night of Bowling & Boone's Farm. Or you may need to rekindle that hard-drug habit you had in your teens. Whatever works for you. Just make sure that you do not sit back at your keyboard and start converting your classes to Hooks.
That's it. There are no other factors to be considered with regard to your legacy, React, class-based codebase.
If this all sounds like a joke, it really isn't. You see, I've been doing a lot of work lately (too much work), mired in the weeds of transmogrifying classes into Hooks. I'm by no means an expert on them, but I've already seen, through gobs of my own code, where Hooks shine - and where they don't.
There's a lot about Hooks that I'm legitimately enjoying. And if you tell me that all of your new code is now done in Hooks, then... Great! I have no problem with that whatsoever.
But let's just be honest with each other here, OK? If you were the kinda person who was content to have one part of your code using a given paradigm, and another part of your code using another very-different paradigm, then you probably wouldn't be a programmer in the first place. It's quite difficult to be a solid, talented developer and not be at-least-a-little OCD about your code.
So the more functional components you write, with all that juicy Hooks goodness baked into them, the more you're gonna feel that Refactoring Demon staring over your shoulder. Every time you write another functional component, and then you have to flip back into a file for a class-based component, your developer eye is gonna start twitching. Little beads of sweat are gonna start breaking out on your forehead. Your foot will start tapping uncontrollably.
Sooner or later... you're gonna think:
Screw it! I'm just gonna put in "a few extra hours" to convert these old class-based components into Hooks.
And once you start down that path, the Refactoring Demon's got you firmly in its grip. And you may not appreciate the end result after the Demon's had its way with you.
If you turn off your headphones for a minute and listen carefully, you can almost hear the fanboys. They're lighting their torches. They're sharpening their pitchforks. And they're already chanting, somewhere off in the distance, something like:
Yer just old and stooopid and you hate modern React!
But this post isn't about "Hooks are Da Sux" or "Hooks are Da Shiznit". This post is about the (meager) relative benefits of converting class-based components into Hooks.
Do you love Hooks?? Awesome! Nothing in this post is trying to dissuade you from that position. (As if I could, even if I tried...) This post is about the question/challenge that's represented by the many millions of lines of class-based code that are already out there, "in the wild".
And I'm here to tell you that you should make every effort to avoid migrating legacy class-based components to Hooks. It has the potential to be a horrific waste of time and energy. And it will probably yield minimal benefits.
Hooks do have benefits, and class-based components do have downsides. And despite what the latest React fanboys will tell you, class-based components do have benefits, and Hooks do have downsides. So if you're faced with a large legacy codebase of class-based components, you have to think very carefully before you blindly dive in with a wholesale rewrite.
Will the benefits of the new Hooks-based components be enough to outweigh the additional time/testing/headache that's necessary to convert your old classes to them? In my experience so far, the answer has been a firm, "No."
In the other posts in this series, I go into more detail explaining the relative merits and demerits of Hooks and classes. So I won't regurgitate all that data here. I will just offer a quick synopsis of a few key points:
Despite the ravings of some who worship at the Church of Functional Programming, functional components are not, hands-down, far-smaller and sleeker. In some cases, the functional components are a little smaller. But too often, what FP enthusiasts call "smaller" is really just "we moved nearly all the code into these 12 separate helper functions."
Some things with Hooks are nominally "better" - like the use of the Context API, and some aspects of state management. But other aspects of functional components are far worse - like lifecycle management.
Those legacy class-based components were written by somebody. That "somebody" might still be on the team - and they might have a far better "feel" for class-based components. Just as functional components might feel better to you, I assure you that there are still people out there - talented, experienced people - who feel the opposite.
But regardless of where you stand on any aspect of Hooks-vs-classes, the simple fact is that:
There is nothing that you can do with Hooks that you simply cannot do with class-based components (or vice versa).
This means that, if you've embarked on a big classes-to-Hooks refactoring project, at the end of said project, you should find that your app... does the exact same thing that it did before. Oh, sure - while you're refactoring, you might decide to add in some new features, or clean up a few lingering edge-case bugs, but these upgrades won't occur because you've moved to Hooks. They will only happen as a side effect of the Hooks migration.
If you Google things like "Hooks tutorial" or "convert React class to Hooks", it's all-too-easy to find a long series of one-pagers that purport to teach you how to migrate from classes to Hooks. And like most tutorials, they're good for getting you started... and not much else. They show you simplistic scenarios that can be converted with the least amount of effort. But the tutorials map only-minimally to "real life" code.
You might be thinking:
Dooood. It shouldn't take long at all to convert a class-based component to Hooks.
And often... it doesn't take that long. My experience with class-to-Hooks migration has followed a typical Pareto analysis. Meaning that, about 80% of the original classes were converted - quickly and easily - with only about 20% of the overall effort. But then you have the other 20% of your class-based components...
When you have a component that's doing a lot of fancy stuff with lifecycle methods, there's a good chance that the conversion to a functional component may not be fun. If you have asynchronous elements (like API calls), that could also be painful. Hooks are very good at state management, but certain types of state tracking are not a one-to-one conversion from classes to Hooks. If you have other large frameworks/libraries stuffed into the middle of your class-based components, it can be quite challenging to "translate" them to Hooks.
With all of the requisite gotchas in mind, many of your class-to-Hooks conversions will be fast, easy, and even pleasurable. But some of them... won't. And the ones that won't will absolutely drag down your migration project.
On several occasions, my classes-to-Hooks migration has triggered a Cascade of Sadness. Maybe you're unfamiliar with that phrase - but I doubt you're unfamiliar with the concept.
You start making the relatively minor syntactic changes that are needed to turn a class into a function-with-Hooks. Then you find that one of your legacy packages doesn't want to play right with the Hooks (non)approach to lifecycle management - so you swap it for something else. Then you realize that some of your class methods should really be refactored into custom Hooks. Then you start chasing down all the places where your new, custom Hook should be used. Then you...
Well, you get the point. Before you know it, the supposedly "simple" migration from a class-based component into Hooks has become a Rabbit Hole of Doom. At some point, you realize that you've been working on this one class, and all of the domino effects that come from changing it, for several days. And all of this is for a component that previously worked just fine as it was.
At this point, I firmly believe that, if you are migrating a large-scale application from classes to Hooks, you're not refactoring it, you're re-architecting it.
Hooks sound like a grand idea - when you're doing "green fields" development. Now look back over your last 5-10 years of professional software work and think of how many times you've been lucky enough to do "green fields" development.
If you decide to implement Hooks, I fully understand why you probably want to rewrite all your legacy components. But Hooks aren't just a new keyword, or a simple shorthand syntax, or an additional convenience function that's been built into the core language.
You've probably read some (or all) of the Hooks documentation that's available right alongside the "regular" React docs. In that documentation, they're very clear in stating that Hooks can live right alongside your old-skool class-based components. And that's true... sorta.
When I look at my Hooks-based components next to my class-based components, they almost feel like they're part of two entirely different frameworks. I've already received feedback from some other React devs who state that they just have a hard time grokking what's going on in the class-based components.
If you're stewarding a legacy React codebase, this can be extremely frustrating. It forces your dev team to switch back-and-forth between two (sometimes radically different) paradigms. Granted, a "senior, experienced, professional" programmer should be capable of making such leaps. But it's usually preferable to avoid such leaps altogether.
I've had the experience of having to work in a PHP codebase that used Drupal in one section of the app, and Joomla in another section. And let me tell you: It sucks.
This is why I have a bit of a problem with the Hooks team's confident statement that you can just start sprinkling Hooks into your codebase wherever and whenever you see fit - and you can just leave all your legacy class-based components as-is. This may be, technically, true. But it's not conducive to crafting a tight codebase or an efficient dev team.
I haven't experienced this yet, but I have a strong fear that, at some gig in the not-too-distant future, I'll be on a team where work on large swaths of the app is commonly delegated to Joe & Mary - because Joe & Mary are the ones who wrote the Hooks-based components, and that's what they're comfortable with. Similarly, work on other large swaths of the app may commonly be delegated to Bob & Susan - because Bob & Susan wrote a lot of the legacy class-based components, and that's what they're comfortable with.
But if you start cranking out a ton of Hooks-based components, right alongside your old class-based components, at some point you're going to have a codebase that looks like that Drupal/Joomla Frankenstein that I described above.
Despite my snarky guidelines at the top of this article, I'm not trying to say that there is never a use-case for rewriting your old class-based components. Here are a few things to keep in mind.
If you have 100 class-based components and 5 new Hooks-based components, do not embark upon some Quixotic quest to rewrite the 100 class-based components. Just... don't. But, if you only have a handful of class-based components lurking around your app, and you've since added 100 Hooks-based components, then I could totally understand the impetus to migrate the class-based components.
Segmenting Into Applications
Have you ever seen a React app with dozens of different modules? And hundreds of thousands of lines of code? And have you ever wondered whether they really should be part of one, monolithic applications?? If so, this might be a prime opportunity to decide which pieces of the original, legacy app can safely be ported off into their own standalone application. Once you've decided that the Big Hairy Legacy App should really be five, unique, smaller apps, it's a prime opportunity to decide which of those smaller apps might be excellent candidates for Hook-ification.
Segmenting Into Modules
If you already have a Big Hairy Legacy App that does many different things, there's a good chance that it's also divided into certain modules. Assuming that's the case, it's much more feasible to decide that this given module will heretofore be a Hooks-based module. It's a much-lighter cognitive load for your fellow devs if they can see that everything in Module X uses classes, and everything in Module Y uses Hooks.
Reserving Hooks For Broader-Scope Refactoring
I've spent a lot of words explaining why it's probably a bad idea to migrate your classes to Hooks. But that mostly applies if you're doing the migration for the sake of doing the migration. In other words, if you're converting a class-based component to Hooks just because you're offended by the mere sight of that scary
classkeyword, then I feel strongly that you should really be leaving that component alone. But there are other, more legitimate, reasons to do refactoring. Most legacy codebases have at least some crotchety old components that are insanely large and complex. New edge-case bugs are found in them every month-or-so - and someone is repeatedly tasked with going in and tinkering with the already-brittle logic. In those cases, you may have been itching to refactor that eyesore anyway. And if you are, it might be an acceptable time to pull out your Hooks-Fu.
Refactoring For Education
I freely admit that I didn't have much "real world" experience with Hooks until a few months ago. When I realized that my new team would be working almost exclusively with functions/Hooks, I took the opportunity to begin refactoring a large side project on which I've been toiling for a few years. I took this approach because I knew there would be "gotchas" and I didn't want to discover those "gotchas" while I was writing/delivering code for my employer (i.e., the place that actually pays me to write code). Quite frankly, I'm glad that I decided to spend all this personal time refactoring a large, personal project - because it's taught me a lot. But this luxury isn't typically available to you when you're working "on the clock". And if you embark on a wholesale classes-to-Hooks migration in the middle of a production project, you'll probably be wasting a lot of your employer's time and money.