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])
}
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]);
}
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]);
}
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)
How is this different from putting the same in the dependency array? We can still put the same three properties like so
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.
Am I missing something here?
Hm, sry, but I've got several nitpicks here:
useEffect()
call when I modify the incominguser
/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.useMemo
even another one. But the costs are neglectable, right?[user]
but actually depending on an unrelatedusername
instead renders your example really messy.[userDetails.username]
, while your callback actually only depends onuserDetails.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.useEffect
hook yourself - it's basically just comparing the previous version of an object (stored viauseRef()
) with the current one, it's no magicI 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.
"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?
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.
This was useful!
Nice article, i'll try to use this today