There's a very simple trick that will 10x your useEffect
hooks' readability, and probably improve their performance, too: use named functions as callbacks for your useEffect
s. You might even find that you don't need an effect at all!
Contents:
TLDR
Do this:
useEffect(
function doSomethingAfterMounting(){ // <--- name me!
// Your effect code
},
[]
);
Not this:
useEffect(
() => {
/**
* Your effect code
*
* Note that this may or may not make
* sense to you in 6 months time.
*/
},
[]
);
What for?
This should provide 3 key benefits:
- More clarity in the purpose of the effect, so other programmers would could easily tell what it's supposed to do just by reading the name;
- If the effect throws an error, the function's name will show up in the stack trace, which should help debugging;
- It should psychologically nudge the programmer into trying to make the effect do only one thing, and to split different tasks into several
useEffects
if necessary;
But before you even write a useEffect
, consider if you even need one! There are plenty of situations where using props
directly, or another hook like useMemo
may make much more sense.
Backstory
useEffect
can be a dangerous hook if you don't know what you're doing. Doubly so because it's usually opaque, with little indication of intent. I can't count how many times I've stared at a giant useEffect
with no idea what it was written to do, or what its authors were thinking.
Once upon a time, I had to refactor a huge amount of logic for a form page. The original author had used useEffect
in exactly the way that React doesn't recommend:
- for updating state based on new props,
- for caching,
- for initializing field values,
- for fetching data from an API,
... and other bad practices.
Even worse, he had often written all of that functionality into just one useEffect
. Clearly, someone had never heard of useMemo
😄 There were several useEffects
that stretched to 50 lines or more. Worse yet, there were often several of them in one file, each of which had its own setState
calls that made the effects dependent on each other. One file had 12 such useEffects
(which, side note, is almost certainly far, far too much code for any single component!)
And alas, that was not the end of it. It turned out that these bad practices were littered throughout the website, on code written by several programmers over many months.
The refactoring took a week, and while it's nice to have some stable work to do, I'm pretty sure both I and the client would've preferred if I had been doing something more productive.
It was around that time that I realized that not only is useEffect
very easy to abuse, but that probably the worst part of it was how opaque it was. I was reading Bob Martin's Clean Code around this time, and noticed that this gentleman had some pretty nice tips!
Among other pearls of wisdom, he advised that:
- all variables (and functions!) should have a clear, informative name:
- functions should be small and short, aim to do one thing, have no side effects, have minimal or no repetition, and should have separated error handling
- the best comment is no comment at all — your code should be self-explanatory in its intention through its concise logic and good naming
It was obvious to anyone with eyes that these principles were being woefully neglected in our client's codebase. So as I did my rewrites, I aimed to break up the enormous components with multiple useEffect
s into smaller, easily understandable parts. One file could turn into two, or five, or even ten. This meant having to figure out what all of those giant effects actually did — and to do that, I often pulled out big chunks of logic into their own, shorter functions. fetch()
effects got their own functions, as did moveUI()
, openModal()
, reorderList()
and many others. It turned out that many of these things didn't even need to be effects, and were thus eliminated.
But other things needed to stay as effects — things like autoClose()
or setHasMounted()
, and I wanted to find a way for these effect hooks to also have names. I aspired to code in a way so that even a person who didn't know how to program (or at least didn't know JavaScript) could see a name and instantly know the point of those few lines.
Comments wouldn't do, so I started out with the simple idea of using a memoized callback that would get automatically called whenever its dependencies changed:
// Idea #1: using a named `useCallback` to add clarity to `useEffect` (don't do this)
const doSomethingInAnEffect = useCallback(() => {
// ... The effect code
}, [])
useEffect(() => doSomethingInAnEffect(), [doSomethingInAnEffect])
This definitely made things better. The effects finally had a meaning, even if they required this superfluous use of a memoized function.
It was at that point that one day it just struck me like a lightbulb: if all I need is a name, why not just use an old-fashioned named function instead of an arrow function? It seemed so simple, and why not? We never needed to use this
inside the effect. And then we could avoid the new useCallback
-hell that was festering. Could it really be so simple? I turned to Google, and as it turns out, I wasn't the first person with the idea (see the sources below). And if it's on Twitter, it must be a great idea, right? 😄
// Idea #2: just using an old-fashioned named function (DO do this)
useEffect(function doSomething() {
// insert effect in here
}, []) // <-- notice: no extra dependencies! :)
As I tried to name my effects, I realized that their names weren't always so obvious — and usually this was because one effect was trying to do too many things. Once I realized this, it became natural to split things apart until each effect did just "one thing" or just got removed completely, and our code quickly got more clearer and simpler as a result. Who knew that naming things well would turn out to be a gift that keeps on giving?
And so, since then, I've always used named functions whenever I needed useEffect
, and I advise everyone else to do so, too.
I don't blame my colleagues (whom I also call my friends) for writing messy useEffect
s. I've written plenty of unmaintainable code, too (haven't we all? 😄). Plus, back in those days, we didn't have awesome guidance articles like "You Might Not Need an Effect" (which I very much recommend you to read, by the way).
I'd also argue that we had inherited the psychological burden of only having one place for effects — namely, componentDidUpdate
— from back in the days when we only had class components, and this may have nudged us into a "write a giant effect that handles everything"-state of mind that didn't always shake off naturally.
So what have we learned? I'd say that this experience taught me a few lessons:
- Make sure to keep your functions as short as possible. Ideally, they should do one thing.
- This tip includes React components, too. If you need to render a giant form, it's probably better to split it up into different files for each section, and maybe even each field.
-
useEffect
shouldn't be treated as a "go to solution", and can be dangerous if you don't know what you're doing. To avoid hurting your app's performance, consider if you can get by with just using theprop
itself, or with usinguseMemo
anduseCallback
. - And finally, while naming things may be one of the only two hard things in computer science, it is absolutely vital for the readability of your code.
- ...Which is why you absolutely must name all of your
useEffect
s with named functions! 😉
- ...Which is why you absolutely must name all of your
Drawbacks
...Are there even any? 😄
So far, I haven't had any issues with this method, but have certainly felt the benefits.
One issue I can imagine occurring is pointer mismatches if you use the this
keyword in your effect. Recall that arrow functions do not have their own this
, but rather use their parent's this
. That said, I have never, ever seen this
being used in an effect, or in fact at all in a functional component... so I guess it's a non-issue? 😄
Conclusion
From what I can see, we should all switch to using named functions in useEffect
, and should do so immediately. This should make our code cleaner and more reliable, and doesn't seem to have any drawbacks. Not only would a simple name let everyone know your intentions, but it should push you to write short, "do only one thing" effects, too.
What do you think? Let me know in the comments! 🙂 And thanks for reading!
Further reading:
- Sergio Xalambrí: 🔥 Pro Tip: Name your useEffect functions
- Cory House: Twitter thread about named
useEffect
s - React documentation: You Might Not Need an Effect
- Robert C. Martin: Clean Code: A Handbook of Agile Software Craftsmanship
- Ashutosh Verma: The Difference Between Regular Functions and Arrow Functions
Top comments (2)
This should be a great way to automate this rule:
github.com/ant1m4tt3r/eslint-plugi...
Awesome stuff! Thanks a lot! 🙂