DEV Community

Cover image for UseEffect dependency array and object comparison!
Yogini Bende
Yogini Bende

Posted on • Updated on

UseEffect dependency array and object comparison!

Hello folks!

If you are performing any side effects in your function, the Effect hook have to be there. This useEffect hook takes first parameter as a function to perform side effect and second parameter, a dependencies array. If you do not wish to perform side effects on every render (which is the case almost every time), you need to pass something to this dependency array or at least an empty array. This array will re-run useEffect, if the values inside it changes. This will work perfectly fine when the values passed in the dependency array are of type boolean, string or numbers. But it will have some gotchas when you are dealing with complex values such as objects or arrays.

Before diving into the solution of the problem, let’s first understand this problem in detail.

React always compares objects and arrays with their reference. This may affect the execution of useEffect in any of these two cases -
1- Object or array is exactly the same, but they are compared using different references.
2- Object or array have different values, but they are compared using the same reference.
In both the cases, useEffect hook will not perform correctly leading to the bugs in our application.

There are possibly two solutions to this. Let’s understand them in detail -

Create dependency on details

Consider an object with all the user details passed as a prop to the function. In your app, you want to perform the side effect only when the username of a user changes. So, in this case, the dependency becomes quite clear! Instead of passing whole user details objects to the useEffect, pass only the detail which matters. Something like this -


function UserProfile({userDetails}) {
    const [error, setError] =  useState(‘’);

    useEffect(() => {
        if(userDetails.username) {
            // Do something…!
}
}, [userDetails.username])
}

Enter fullscreen mode Exit fullscreen mode

This way, useEffect will compare the exact value and will re-run only when the username changes. This approach is suitable for small no of dependencies, but will not be clean and scalable if the array grows.

Memoizing the object

One more solution for this problem could be creating a new object every time. That way, we can always be sure that all changes are recorded and comparison is not happening on reference of that object. To better understand, let’s see the code -

function UserProfile({ userDetails }) {
    const [error, setError] = useState('');
    const [user, setUser] = useState({
        username: userDetails.username,
        email: userDetails.email,
        address: userDetails.address,
    });

    useEffect(() => {
        if (userDetails.username) {
            // Do something…!
        }
    }, [userDetails.username]);
}
Enter fullscreen mode Exit fullscreen mode

As you see in code, we created another copy object of the same object. Though this seems to solve the issue, there is a problem with this approach too. We all know creating objects in javascript is an expensive operation and we are trying to do that twice!! Apart from that, we are also duplicating the code which is again not a good practice to follow.

To solve all these problems, memoizing the object becomes a very simple and easy to maintain solution. Let’s see how -

Memoizing an object means we try to maintain a memory of the object. In better terms, we cache an object and maintain its one copy in our function. When the function re-renders, this same copy will be used in our function until any property of that object does not change. This way, we minimize the expensive operation of creating objects and also maintain an approach to catch the change.

For this memoizing, we use useMemo hook react. Let’s see the code -

function UserProfile({ userDetails }) {
    const [error, setError] = useState('');
    const { username, email, address } = userDetails;

    const user = useMemo(() => createUser({ username, email, address }), [
        username,
        email,
        address,
    ]);

    useEffect(() => {
        if (username) {
            // Do something…!
        }
    }, [user]);
}
Enter fullscreen mode Exit fullscreen mode

As in the above function, the createUser function will get called only when the username, email and address changes and new User object will be created.This way we make sure to compare correct copy of object in the dependency array and also optimize the unnecessary re-renders,

This is one of the tricky topics while working with useEffect as we tend to miss the fact that react will compare the reference of the object. Passing objects directly to useEffect will make the function buggy and we end up spending a lot of time figuring out what exactly is wrong!! (happened with me a lot!)

That’s it for this article and I hope it has helped you in some way! Please let me know how you solved the problem while passing objects to the dependency array? And of course, thoughts/comments/feedback on the article :)

You can also connect with me on Twitter or buy me a coffee if you like my articles.

Keep learning 🙌

Top comments (6)

Collapse
 
nnk1996 profile image
nnk1996

How is this different from putting the same in the dependency array? We can still put the same three properties like so

  useEffect(() => {
        if (username) {
            // Do something…!
        }
    }, [userDetails.username, userDetails.email, userDetails.address]);

Enter fullscreen mode Exit fullscreen mode

This feels like a very roundabout way, not to mention the extra caching space that we are occupying. if there is a issue that the specified property may not be available at some point we can do optional chaining.

  useEffect(() => {
        if (username) {
            // Do something…!
        }
    }, [userDetails?.username, userDetails?.email, userDetails?.address]);

Enter fullscreen mode Exit fullscreen mode

Am I missing something here?

Collapse
 
jonasdoe profile image
JonasDoe • Edited

Hm, sry, but I've got several nitpicks here:

  • Initially you explain that one shouldn't use non-primitives as dependencies. I fully agree on that, but step by step you seem to shift with your solution to "how to prevent a unwanted useEffect() call when I modify the incoming user/userDetails internally?". But in my experience, this is rarely the case and even rarer the problem of ppl. wondering how utilize "UseEffect dependency array and object comparison". Non-primitives won't work even if I copy them into a state or memoize them, but that might be the (wrong) take-away for some newcomers.
  • You don't give any source/proof for the claim that creating objects is a expensive. Tbh. I think the opposite is true. And thinking about it, you create a new dependency array on every call, and with useMemo even another one. But the costs are neglectable, right?
  • You say that long dependency arrays aren't clean. I'm inclined to disagree. They might be verbose, but they still are the cleanest solution. In opposite, I think, creating memoized objects or states is way harder to read (apart from the fact they aren't really helping in most situations, as mentioned above). And having a dependency array like [user] but actually depending on an unrelated username instead renders your example really messy.
  • In some examples, you dependency array is [userDetails.username], while your callback actually only depends on userDetails.username == null. I'ld recommend to keep things parsimonious. I know, I know, you've just presented a truncated example, but I found it still somehwat misleading.
  • In my opinion the real take-away for an article with a topic like yours should be: a) just go for it and put all props you depend on in the dependency array, or b) stringify the object you're depending on (might be slow and unreliable) or c) utilize an external library like github.com/kentcdodds/use-deep-com... or write a custom useEffect hook yourself - it's basically just comparing the previous version of an object (stored via useRef()) with the current one, it's no magic

I mean no offense, but I've noticed that unexperienced people keep stumbling upon this article when wondering about non-primitive dependencies, and without some clarification it won't really help them.

Collapse
 
axelthat profile image
Axel

"We all know creating objects in javascript is an expensive operation and we are trying to do that twice!!"

Creating objects in javascript is expensive? How come? Am I missing something here?

Collapse
 
zyabxwcd profile image
Akash

Nice trick there with the memoization. Never thought of using it this way. I have seen developers taking the dependency array for granted and these were not beginners. Interestingly beginners seem to forget about dependency far less and that too due to a mistake. The ones that were doing this, were seasoned (as in they have been programming for a while now) developers ignoring the dependency array and I am really against this practice. If something doesn't seem to be going wrong just by looking at the application now, doesn't mean it never will or there aren't some unnecessary re-renders taking place. Its really nice to see someone paying attention to it, realising its importance and using it cleverly to his/her advantage.

Collapse
 
vaibhavkhulbe profile image
Vaibhav Khulbe

This was useful!

Collapse
 
buriti97 profile image
buridev

Nice article, i'll try to use this today