UseDeepEffect Hook in React: A Guide to Better Performance

Learn how to use the useDeepEffect hook in React to optimize your app's performance. Get step-by-step instructions for implementing this powerful hook.

#react
#javascript

A really common situation when working with React Hooks is that we need to enhance their basic functionality to fit a requirement imposed by the component behavior, even if the native Hook implementations are really useful and cover most of what we need. The Hooks provided by React can be combined to create custom Hooks to support complex cases.

The Problem

Let’s say we have a component that receives an object as a prop, and we want to run an effect every time one of the properties in this object changes. We can write some basic code to get a better idea:

jsx
import React, { useEffect, useState } from 'react';

function Parent() {
const [count, setCount] = useState(0);
useEffect(() => {
// Update count every second with a setInterval
});
return <Child data={{ firstName, lastName }} count={count} />;
}

function Child({ data, count }) {
useEffect(() => {
console.log('Data has changed!');
}, [data]);

return (
<div>
<p>{data.firstName}</p>
<p>{data.lastName}</p>
<p>{count}</p>
</div>
);
}

At this point, we pass some data into an object and a count value to the child component, and it should run the effect only when one of the values firstName or lastName changes, right? Nope!

The useEffect Hook doesn’t perform a shallow comparison on objects, but it checks whether its reference changes or not.

If we take a better look at the code above, you’ll see that Parent always creates a new object when passing the props, so every time the count variable changes and the child component gets re-rendered, the effect will run even if there’s not a real change in the values of the data property.

We need a way to run the effect only in the case that the content of our object changes — something that checks the object properties.

The Solution

Based on the requirement, we need a special Hook that checks if any of the passed dependencies has changed its content. We can write the useDeepEffect Hook, and then I’ll comment on its implementation.

js
import { useEffect, useRef } from 'react';
import isEqual from 'lodash-es/isEqual';

export function useDeepEffect(effectFunc, deps) {
// 1° Step
const isFirst = useRef(true);
const prevDeps = useRef(deps);

useEffect(() => {
// 2° Step
const isSame = prevDeps.current.every((obj, index) => isEqual(obj, deps[index]));

// 3° Step
if (isFirst.current || !isSame) {
effectFunc();
}
// 4° Step
isFirst.current = false;
prevDeps.current = deps;
}, deps);
}

To implement this custom Hook, we are using the Lodash function isEqual, which will help us to deeply compare two passed elements if they are of the object type. Let’s recap how it works:

  • Declare an isFirst reference using the useRef Hook to keep track of the status of the effect. We need this later to check if it is the first time it should run. We do the same with the prevDeps ref to always have a reference to the previous dependencies the Hook should review.
  • Using the Array.propotype.every method, iterate over the current dependencies array and compare each one of them with the previous value. Store the result in a variable to identify if anything has changed.
  • It’s time to decide if we should run the effect function or not. It should run only if it’s the first time or if the isSame variable is false, meaning something has changed from the previous dependencies.
  • In the end, we should ensure it’s not the first time the Hook runs. We also have to update the prevDeps reference with the last dependencies we just used for the comparison.

Wrapping up

Now you can replace the normal useEffect with the new useDeepEffect we just declared. This Hook is used in many situations. The one explained above is a simple example, but it’s a good one to understand a situation for its usage.

Last updated: