The Botched Implementation of useEffect()
Tags: hooks, react, javascript, functional
Published: 2020-09-08 14:06:15

If you're a React dev, and you love those tasty Hooks, we need to talk.  And just like when your significant other texts you, "we need 2 talk", this little discussion will probably be uncomfortable.  You may even become defensive.  But we really need to have a heart-to-heart about `useEffect()`.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1xokdg7noujiby2hia01.jpg)
## Fanboys Be Gone
Before I dive into the Epic Sadness that is `useEffect()`, I'm gonna throw out a few critical disclaimers for all the Keyboard Kommanders out there who are cracking their knuckles and preparing to give me a righteous beat-down.  

First, I use Hooks.  All the time.  In fact, at this point, I _only_ use Hooks and function-based components.  So please don't waste your time trying to defend Hooks.  Do you love them?  Great!  So do I!  This post has nothing to do with being a Hooks Haterrr.  

Do you hate classes??  Well, I don't entirely share that hatred, but nothing in this post is in "defense" of classes.  So again... don't waste your time trying to defend `useEffect()` by pointing out that "classes are da sux".

I'm not trying to talk about React vs. Svelte vs. Vue.  Or functional programming vs. OOP.  Or tabs vs. spaces.  Or chocolate vs. strawberry.  I'm not (in _this_ article) trying to dive into any broader Holy Wars.  In this article, I'm simply examining the shortcomings of one particular language feature: `useEffect()`.

So with those disclaimers out of the way, let's talk about how `useEffect()` came about...
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/axh0nyat2wzc33gg4zuz.jpg)
## In The Beginning
In the beginning, there were class-based components.  And they were good.  (OK, OK.  I get it.  A lotta React devs have some kinda deep-seated hatred for anything that uses the `class` keyword.  So maybe you don't think that class-based components were good at all.  But they undeniably _worked_.  If they didn't, React would've died on the vine, _years_ ago.)

And in the beginning, class-based components had lifecycle methods.  And they were good.  (OK, OK.  I get it.  Lifecycle methods could, at times, be a complete PITA.  Every three days, another lifecycle method would get renamed to something like: `THIS_IS_UNSAFE_AND_YOU_SHOULD_NEVER_USE_IT()`.  And lifecycle methods are fabulous at creating unintended, endless re-renders.  But there were many critical tasks in the development of large-scale React apps that simply could _not_ be done without lifecycle methods.)

On the seventh day, the React team rested.  But they started worrying about the continual stream of fanboys who were wringing their hands over that ugly, nasty `class` keyword.  And they decided to correct this "design flaw" with a great flood known as _Hooks_.  

Hooks wiped away all the nastiness that came from those unconscionable class-based components.  Now the FP fanboys could rejoice with all of their _pure functions_.  And they could rejoice at never having to use the `class` keyword again.  But along the way, a critical piece of functionality was left off the ark.  In the rush to adopt all-functions-all-the-time, React's lifecycle methods were treated as... an afterthought.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bvti5l54dt68qlmw2oj8.png)
## Naming Things Is Hard
The first clue that something might be amiss with `useEffect()` actually comes from its name.  Any time you have a core feature with an impossibly-vague name, it's a sign that the feature _might_ be trying to do too much.  

Several years ago, I worked with a guy who was fond of naming methods `doWork()`.  I don't mean that he created methods that were something similar to `doWork()`.  I mean that I'd look at the classes he'd created (we were writing in Java) and, quite frequently, the class would contain a method that was named, literally, `doWork()`.  In fact, it was not uncommon to find that the majority of the class's logic was contained inside `doWork()`.

What exactly did `doWork()` _do_???  Well, obviously, it did... _work_.  Ohhhh... you want to know what _type_ of work it did???  Well, that could only be understood if you spent copious hours reading through each of the `doWork()` methods on your own.  Because, unlike methods such as `calculateSalesTax()` or `saveShoppingCart()`, there's no way to see a call to a `doWork()` method and have any clue about what _exactly_ is expected to happen.  

So what happens when `useEffect()` is invoked???  Well, umm... it creates... an _effect_.  What _kind_ of effect?  Yeah, well... you'll just have to read through the code on your own to figure that out.  Because the function name itself provides no such information.  `useEffect()` is barely more descriptive than `compute()` or `useLogic()`.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ja86yg6h9lgsl3wkns4c.jpg)
## Lost Time
Maybe this sounds like an unfair critique, because the lifecycle methods that came with class-based components also didn't tell you what happens inside them.  What happens inside `componentDidMount()`???  It's impossible to know, based on nothing more than the method name.  What happens inside `componentWillUnmount()`???  It's impossible to know, based on nothing more than the method name.

But the names of lifecycle methods don't tell us _what_ happens.  They tell us _when_ it happens.  I don't know exactly what logic you put inside that `componentDidMount()` method, but I can tell you, with absolute certainty, that it will happen immediately _after_ the component is mounted.  And I don't know exactly what logic you put inside that `componentWillUnmount()` method, but I can tell you, with absolute certainty, that it will happen immediately _before_ the component is unmounted.

_When_ does the logic inside `useEffect()` get invoked??  Well, ummm... that depends.  It depends upon _how_ you call it.  I won't rehash that all in this article.  Instead, I'll just refer to another blogger's excellent article right here on Dev.to:  https://dev.to/spukas/4-ways-to-useeffect-pf6

As you can see in the linked article, `useEffect()` could happen on mount, or after all renders, or after _some_ renders.  And if you include a `return` statement, it could also trigger logic on unmount.  

I've been programming for a quarter century and I've been doing React for more than five years.  But I have the article above bookmarked because I find myself routinely referring back to it.  I have to constantly remind myself how the exact same language construct can be used to invoke four very-different behaviors.

This array of behaviors happens because you have a _single_ Hook designed to replace the methods which previously governed _multiple_ stages in a component's lifecycle.  On a practical level, this means that you can't immediately tell _when_ `useEffect()` will be called - until you manually assess the _way_ in which it's being called.  

_Ughh..._
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kfi6vnt8kzs19c0veee5.jpg)
## Doing Too Many Things
As long as I've been programming, there's been a basic aphorism that has helped me, time and time again, to write better code:
<br/><br/>

> A function should do one thing, and _only_ one thing - and do it well.

<br/><br/>`useEffect()` violates this basic tenet.  As already discussed, I fully understand that its name will never tell me _what_ it's doing.  But I can't even tell (from the name) _when_ it's doing.  

It can't possibly tell me _when_ it's doing, because it's trying to replace _all of the lifecycle methods_ that existed in class-based components.  It's trying to replace them all - with a single function.  It's like the Hooks team hired my former colleague who constantly wanted to write `doWork()` methods.  But now, he just writes `useEffect()`.  
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/23lq9fd579lxcvlekhey.jpg)
## Missing Pieces
There are many reasons to discourage writing functions that `doALLTheThings()`.  One of the strongest reasons is that, any time you _try_ to `doALLTheThings()`, invariably, you end up leaving something out.  

Sometimes it's an error of omission.  Other times, it's an error of outright arrogance.  In other words, when you try to `doALLTheThings()`, it's inevitable that, eventually, someone notices a key feature that's been left out of `doALLTheThings()`.  And just as inevitably, there's a strong tendency for the function's programmer to reply that, "You don't actually _need_ that feature."  

This is what's happened with `useEffect()`.  First, we get this kind of arrogance from the Hooks Crowd:

**Class Crowd:** I need to use `componentWillMount()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `componentDidMount()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `componentWillReceiveProps()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `shouldComponentUpdate()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `componentWillUpdate()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `componentDidUpdate()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `componentWillUnmount()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `getDerivedStateFromProps()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `getSnapshotBeforeUpdate()`.
**Hooks Crowd:** Just use `useEffect()`!!!
**Class Crowd:** I need to use `componentDidCatch()`.
**Hooks Crowd:** Just use `useEffect()`!!!

Second, if they can't explain exactly _how_ `useEffect()` replaces a lifecycle method, they just dismiss the feature altogether by stating that we don't _actually_ need it.  I saw this not-too-long ago when I wrote an article about the lack of _constructors_ in functional components.  (You can read it here: https://dev.to/bytebodger/constructors-in-functional-components-with-hooks-280m)  

[FWIW, I understand that a constructor isn't often defined as a "React lifecycle method".  But with class-based components, it absolutely _is_ a quantifiable stage in the component's lifecycle.]

The Hooks documentation states that, because you can initialize a state variable inside the `useState()` call, there's no need for a constructor.  Of course, this relies on the (extremely shortsighted) idea that initializing state variables is the only logical use for a constructor.  

Furthermore, there were commenters on my previous article who seemed baffled as to _how_ a constructor would even be used in a functional component.  The clear inference was: "_I_ don't personally have a use for a constructor-like feature in a functional component, so _you_ shouldn't need it."

But something funny happened after I wrote that article.  It's gained a steady stream of new readers, week-by-week and month-by-month.  

That may not sound intriguing to you, but on Dev.to, most articles get nearly all of their views within the first week-or-so after they're published.  In fact, I've written many articles that can now go months without registering a single new view.  But my constructor article keeps getting more views - so many, in fact, that it's now my most-read article.

What does that tell us??  Well, I interpret it to mean that there are a lotta people out there _googling_ how to do a constructor in a React functional component.  Since there aren't many other articles written about it (and since the official Hooks docs basically tell you to _get over it_), they end up finding my article on the subject.  

In other words, I'm not the only person out there who feels that `useEffect()` doesn't magically replace every single lifecycle method that we had at our disposal in class-based components.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/91qhen8yww09h4hqrqo6.jpg)
## (Over) Simplification
In general, simplification in programming is a _good_ thing.  "Complex" code is usually synonymous with "bad" code.  Complexity breeds bugs.  Complexity increases costs.  And time (which is... a cost).  

But simplification can go too far.  When simplification obfuscates what's _actually_ happening, the simplification itself can be an obstacle.  If simplification keeps us from implementing critical features, it can actually _increase_ our costs.  And our time (which is... a cost).  

In many ways, `useEffect()` oversimplifies (obfuscates) the React lifecycle that is always there - whether you're writing class-based _or_ functional components.  It's entirely possible that _all_ of those old lifecycle methods aren't necessary.  _Maybe_, some of them cause more problems than they solve.  But those lifecycle methods represent a fine-toothed tool by which we can peer into - and "adjust" - much of what's happening "under the covers" during that magical React update cycle.  

You may strive to avoid using lifecycle methods.  You may write thousands of LoC without ever reaching for one of them.  But man, when you _really_ need one of them, they can be a lifesaver.  And even if you never actually use any of those lifecycle methods, merely _knowing_ about them and _understanding_ their (potential) use provides greater insight into the inner-workings of React.  

But trying to throw it all behind a single, utilitarian, Swiss army knife function like `useEffect()` is like having a software company tell you, "Don't you worry about any of that.  We'll just make it all... _work_.  You don't need to do anything at all.  We'll just `doWork()` for you." 

I've already noticed this effect when I'm talking to some React devs who gulp from the _functional programming_ fountain.  They write their functional components, and they sprinkle them all with Hooks, and then they talk as though there _is no real React lifecycle to be concerned with_.  And to be clear, I kinda understand why they think this way.

In vanilla JS, a function has no lifecycle.  You call it - or you don't.  It runs - whenever you call it.  And it doesn't run if you don't call it.  It's that simple.  But in React, functional _components_ are not so simple.  

React's functional components typically `return` some kind of JSX. And when they do, that rendering cycle is handled somewhat automagically under the covers by React's virtual DOM.  In fact, I wrote a separate article about the _reconciliation_ process here: https://dev.to/bytebodger/react-s-render-doesn-t-render-1jc5  (And yes, I know that it's _possible_ to trace every single re-render - but it's also a complete PITA that can sometimes be difficult to track and understand.)

The reconciliation process doesn't go away if you switch from class-based components to functional components.  It's still there.  Under the covers.  Working the same as it ever has.  

The difference is that, in class-based components, there's this rich library of lifecycle methods that allows us, if necessary, to get our fingers into the gears a bit.  And even when it's _not_ necessary, the lifecycle methods serve as a kind of living documentation that highlights all the steps a component goes through during the entire rendering/updating/reconciliation cycle.

But with Hooks, we just have one, vague, amorphous "lifecycle" method called `useEffect()`.  It's a helluva lot like opening all your classes and seeing nothing but `doWork()` methods.