Tips for Using React’s UseEffect Effectively

Dependency array, code optimization, useCallback, and more

Nivedha Duraisamy
Better Programming
Published in
4 min readJun 10, 2020
Photo by James Harrison on Unsplash

Hooks are a great way to access React’s state and lifecycle methods from a functional component. The useEffect Hook is a function (effect) that runs after render and every time the DOM updates.

In this article, we’ll discuss some tips to better use the useEffect Hook.

1. Child Effects Fire First

Think of the useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

So the useEffect Hook behaves similarly to the class lifecycle methods. One behaviour to note is that the child callback is fired prior to the parent callback. You can check this behaviour here.

Say you have to trigger a payment automatically. This code is written in the child component that runs after render, but the actual details (total amount, discount, etc.) required for payment is fetched in the parent component’s effect. In this case, as payment is triggered before setting the details required, it won’t be successful.

Bear this in mind and structure your code accordingly.

2. The Dependency Array

Let’s start with the basics. The useEffect Hook accepts a second argument, known as a dependency array, to control when the callback should fire.

Running effects on every DOM update

Not passing in a dependency array will run the callback on every DOM update.

Running effects on initial render

Passing in an empty array runs the effects only after the initial render. By this time the state is updated with its initial values. Further updates in DOM does not call this effect.

This is similar to the componentDidMount and componentWillUnmount (on return) lifecycle methods.

This is where all the listeners and subscriptions required for your page is added.

Running effects on specific props changes

Suppose you have to fetch data (product details) based on which product a user is interested in — the selected product has a productId, for example. We need to run the callback every time the productId changes — not just on the initial render or on every DOM update.

This basically replicated the componentDidUpdate lifecycle method.

We can also pass multiple values to the dependency array.

A classic counter example will help us better understand this:

In the example above, an update in counter1 or counter2 would trigger the below effect.

Passing objects in dependency array

Now, what if your callback is dependent on an object. Would our effects run successfully if you do this?:

The answer is no — because objects are reference types. Any change in the object’s property would go unnoticed by the dependency array because only the reference is checked but not the values inside.

There are a few approaches that you can follow to perform deep comparisons in objects.

  • JSON.stringify the object:

Now, our effect could detect when an object’s property changes and function as expected.

  • useRef and Lodash for comparison:

You can also write your own custom functions for comparison using useRef. It is used to hold mutable values throughout the component's lifetime in its current property.

You can read more on useRef if you’re not familiar with it.

  • External packages

If your object is too complex to do a comparison yourself, you can use npm packages to do it for you. A popular suggestion is use-deep-compare-effect:

The useDeepCompareEffect will do a deep comparison and run the callback only when the object obj has changed.

3. Multiple useEffect for Cleaner Code

Now that we know about the dependency array, we might need to separate effects to run on different lifecycle events of a component or simply for cleaner code.

As already seen in the demo earlier, it is possible to write multiple useEffect in a single component.

4. Handle Dependencies in Functions

Suppose you want to break down the code into smaller functions and call it from effect, as below:

This would not give us the expected behaviour — doSomething is dependent on data which is not included in the useEffect array. Any update to data will not fire our callback.

This is why React recommends we have the function within the useEffect as its easier to trace the dependencies.

But what if you want to write reusable functions? Or pass it down from a parent component?

When you want to pass a function as a prop to its child component, you need to add the function to the dependency array in the child component’s effect. But every time something changes in parent, new instances of these functions are created and our callback is fired. This is inefficient.

For scenarios like these, useCallback comes to the rescue.

Similar to useEffect, the useCallback accepts a callback and an array of dependencies. It will return a memoized version of the callback that only changes its identity if any of the dependencies has changed, ensuring we don't create a new instance of the function every time the parent re-renders.

You can experiment with the above here.

5. Always Call Hooks at the Top Level

You cannot call useEffect(or any Hooks) inside conditional, loops or nested functions.

You can read more on rules of Hooks.

TL;DR

All the points discussed above are in this code. You can play around with it.

Thanks for reading. Happy coding!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Responses (4)

What are your thoughts?