4 Tips to avoid pitfalls when using useEffect in React

useEffect is the most widely used hook in React. It is handy and looks easy to use, but you must experience infinite loops or some unexpected result when using useEffect. In this article, I try to give you some tips to avoid those typical pitfalls when using it.

Tip #1 understand the mind concept of useEffect

The definition of useEffect is below:

There are three factors in useEffect, effect, cleanup, and deps.
The key point is effect. We all know useEffect is to wrap side effects something like data fetching, DOM operations, etc. To be honest, when I read this at very first time, I didn’t quite understand, util I realised that action wrapped by useEffect actually will be executed after the normal render process. It is more like the below:

Another factor is cleanup , and it will be called once the component umount from the DOM tree, something like when we close a tab or when we redirect to another page. cleanup is like below:

deps is a simple plain list. React will record the valuation of what it ran last time. And it will compare each item in the list to the previous list when re-render happens. If there are the same, the re-render process will skip effect, otherwise it will execute effect again. deps looks quite easy, but it is the most error-prone part in useEffect, and the most issues come from this.

Tip #2 Pay extra attention to the object or function items in dependency list

Most useEffect issues come from deps, and let’s take a close look at deps.
As we already know, deps is just an array with many items of any types. And when determining if the effect need to be executed, it compare each item with strict equality manner (triple equals). So the determination process is more like below:

Please pay attention to the strict equality ===. It is quite straight forward for most primitive types like number, string, boolean, null, or undefined. However, for object or function, it will compare the address. Even the object values are exactly the same, but we create an object using literal way, it will be treated as different. So when object or function appears in the deps, pay extra attention.
The following is a very obvious infinite loop error:

Maybe you think you will not do the above fault. But when nesting components and pass callbacks along the way, it is easy to make such mistakes.

Tip #3 Make the function less dynamic

Function is the 1st citizen in Javascript, so it can be created dynamically. If we create a function in functional component, and was used in effect, it means it will trigger the effect every time the component is re-rendered. The following is an example that the sendMessage function will be created in every render process.

If we need this function in certain effect and of course we need to put it in effect dependency list, it will potentially cause extra effect execution issue. The reason behind is if the effect is triggered by other factor cause re-render and the sendMessage function will be changed which trigger the effect execution again. It might not be a big issue, unless it will be a problem if sending message twice.

The following is the whole code to simulate the issue, or can visit code sandbox.

Basically, there is an Animals component, which present the animals, and will tell the parent the animal weight and also need to send a message with the weight.

Check this screen shot below for the problem. Did you see the console.log twice for each message?

The root cause is the effect of the Animals component, it will trigger the parent App component to re-render, because the sendMessage function defined in App component, it will change each time when App re render, which trigger the execution of the effect Animals component again.

So, how to fix this issue? The answer is to make the sendMessage function less dynamic, there are two ways to make the less dynamic:

  1. Move sendMessage function out of the component like below:

2. Use `useCallback` function to make it static like below:

Tip #4 Make the object less dynamic

If an object appears in the dependency list of the effect, we need to pay attention esp. when this object is varying.

The below is an example that a varying object in dependency list cause the sendMessage fired twice, or you can visit code sandbox.

This time it is not because of the dynamic function, it is because of the varying object.

The root cause is the effect of the Animals component, it will trigger the parent App component to re-render, because the animals object defined literal way, it will change each time when App re render, which trigger the execution of the effect Animals component again.

To fix the issue, there are several ways as below:

  1. Move the `animals` definition static, i.e., put it outside.

2. Introduce customised `useDeepMemo`, which will create a memo based on if the object value changes.

And the useDeepMemo is below:

Or please check the code sandbox for all the source code.

Written by

Full Stack Dev

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store