React Tricks: Customizing your useEffect to run ONLY when you want!

How the useEffect Hook Works (with Examples)

INTRODUCTION

The useEffect hook in the world of React’s functional components operates as a componentDidMount, componentDidUpdate and componentWillUnmount – all in one. But what if you only wanted to trigger one of the effects at a time. How would you accomplish this? Before we dive into it, let’s understand a little more about how the useEffect works.

Useeffect – render cycle & dependencies

First let’s understand how the useEffect works in the render cycle. Look at the component below, and think about 3 questions – In what sequence will the console logs be printed:

  1. When the component mounts?
  2. When the count is incremented?
  3. When the component unmounts
SOLUTION
When the component mounts:
  1. Starting of component 0
  2. Before return 0
  3. Mounting the component 0
  4. Count set to 0
When the count is incremented
  1. Starting of component 1
  2. Before return 1
  3. Count cleanup 0
  4. Count set to 1
When the component unmounts
  1. Unmounting the component 0
  2. Count cleanup 3

Did you get it right? I didn’t!

Here’s what we learn from this experiment:

  1. None of the useEffects run till after the component has executed the return statement. That’s why “Starting of component” and “Before return” were logged before “Mounting the component” and “Count set to 0”.
  2. Once the component has rendered i.e. returned the JSX, it runs the useEffects in the order that they were declared in (this can be very useful to know, as we will see)
  3. When the count is incremented, the console logs at the beginning and end run before the useEffect (and they run with the updated value of count!). That means, even when the component state changes, useEffects run only after the component has executed the return statement.
  4. After the count is incremented and the return statement executed, the cleanup function returned in the useEffect for count runs – but it runs with the old value i.e. 0. Finally, after the cleanup, the main body of the useEffect runs with the new value of count i.e. 1.
  5. When unmounting, ONLY the functions returned from the useEffects run with the value of count as per what it was when the useEffect ran lasts. For the unmount cleanup effect with no dependencies, the count printed 0. For the count cleanup effect, the count printed 3 (which was the last value of count)

This exercise should have clarified a lot about how useEffects work. Notably, the cleanup function will run everytime the state of one of the dependencies changes and not only when the component unmounts.

Here’s another situation – all we have done is declared a stable object to the component and mentioned it in the dependency array. What do you think will happen now as the component state changes?

SOLUTION
WHEN THE COMPONENT MOUNTS
  • Mounting the component 0
WHEN THE COUNT IS incremented:
  • Unmounting the component 0
  • Mounting the component 1

Wait! Whaaaat?

Why is the effect running when the count changes, even though it’s not in the dependency array? Because, everytime the state changes, the variable stableObject is declared again. When it is declared again, it’s address/reference changes. When the component checks whether or not to run the effect again, it compares the old instance of stableObject and the new one. Although the values are the same, since the references are different, React feels that the effect needs to run again. This is something you need to be mindful of while writing your useEffects. As far as possible, ensure that you have primitives in the dependency array, and if you don’t, ensure that your variables have a stable signature by wrapping them in a useMemo.

Now that we have a grasp on how the useEffect works, lets move on to configuring it as per our requirements.

RUNNING AN EFFECT ONLY ON MOUNT

If you want to run an effect only on mount, and the effect depends on a stateful variable, all you need to do is declare the useEffect without that variable in the dependency array. This is completely okay! The effect will run with the initial value of the state variable and won’t run again.

The only thing to be aware of is that if you have a cleanup function returned from the effect, it will also run with the same initial value of count – and not the updated one.

SKIPPING AN EFFECT ON THE FIRST RENDER

Many a times, we come across situations where we don’t want to run an effect when the component mounts, but only when its dependency changes thereafter. Here’s how you can do that.

We could accomplish the same thing using the useState hook instead of useRef variable. But that would cause a re-render immediately when the component mounts. When a ref is changed, it doesn’t cause a re-render.

Running a cleanup only once on unmount

Here we use a very similar technique to control the unmount. Important to note here, the order in which you declare the useEffects is critical. If you swap the order of the useEffects, the cleanup effect will not work. This is because the useEffects and cleanups run in the order that they are declared.

MANAGE IT ALL WITH A CUSTOM HOOK

Here’s a custom hook you can use to manage when your useEffects run!

Hope this was helpful!

For more awesome content on React visit blog.joshsoftware.com.

4 thoughts on “React Tricks: Customizing your useEffect to run ONLY when you want!

    1. Hi Michal. Thanks for your comment.
      So there are 2 ways of handling this situation. I’m outlining them below. Please note that whenever I mention object, you can consider it as an object/array.
      1. If the object holds static values, (eg. const stableObject = {a:1, b:2}) that don’t rely on any variable in the component, I would recommend declaring it outside the component altogether. That’s the safest way of doing it. Once it is declared outside the component, the component will not be declared again when the app rerenders. It will simply reference the object outside. This is what we should have done in this case.
      2. If the object holds dynamic values that depend on other variables (eg.
      const int1 = 1;
      const int2 = 2;
      const dynamicObject = { a: int1, b: int2 }
      )
      You can memoize it as follows:
      const dynamicObject = useMemo(() => {
      return {a: int1, b: int2}
      }, [int1, int2])

      But again, this will only work reliably when int1 and int2 are primitives. If you have a use case that looks like this:
      const dynamicObject – useMemo(() => {
      return {…otherObj, modified: true}
      }, [otherObj])

      You will need to ensure that otherObj is memoized correctly.
      To understand useMemo better, you can use this blog:

      Optimization Techniques for better Performance: React


      Also, if you use a library like ImmutableJS or Immer for state management (correctly), it will take care of memoization for you. For more on that you can go through:

      Immutability with Immer in React

      Native vs ImmutableJS vs Immer — Are libraries the way to go for immutability in React?

      Hope this helped. Let me know if you have any more questions.

  1. Hey, very useful article! Could you explain more how to correctly use useMemo hook in example with stableObject or with arrays of that objects?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.