DEV Community

Cover image for React Pro Tip #1 — Name your `useEffect`!
Deckstar
Deckstar

Posted on • Updated on

React Pro Tip #1 — Name your `useEffect`!

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 useEffects. 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 
  },
  []
);
Enter fullscreen mode Exit fullscreen mode

Not this:

useEffect( 
  () => {
    /**
    * Your effect code
    * 
    * Note that this may or may not make
    * sense to you in 6 months time.
    */
  },
  []
);
Enter fullscreen mode Exit fullscreen mode

What for?

This should provide 3 key benefits:

  1. 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;
  2. If the effect throws an error, the function's name will show up in the stack trace, which should help debugging;
  3. 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 useEffects 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])
Enter fullscreen mode Exit fullscreen mode

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! :) 
Enter fullscreen mode Exit fullscreen mode

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 useEffects. 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 the prop itself, or with using useMemo and useCallback.
  • 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 useEffects with named functions! 😉

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:

Top comments (2)

Collapse
 
airtonix profile image
Zenobius Jiricek

This should be a great way to automate this rule:

github.com/ant1m4tt3r/eslint-plugi...

Collapse
 
deckstar profile image
Deckstar

Awesome stuff! Thanks a lot! 🙂