Understanding useEffect and Shallow Comparison in React

In React, managing state and side effects can be challenging, especially when dealing with re-renders. In this post, we'll explore the useEffect hook and how JavaScript's shallow comparison impacts re-rendering in React components.

What is useEffect?

The useEffect hook allows you to perform side effects in function components. This can include data fetching, subscriptions, or manually changing the DOM. The effect runs after the render and can be controlled using a dependency array.

useEffect(() => {
  // Your side effect code here
}, [dependencies]);

How Shallow Comparison Works

In React, components re-render when their state or props change. React performs a shallow comparison to determine whether the values have changed. This means it only checks the first level of the object or array, without going deeper into nested structures.

Primitives vs. Objects

When dealing with primitives (like numbers, strings, and booleans), React can easily determine if they have changed since they hold a single value. For example, if you update a count state from 1 to 2, React recognizes that a change has occurred.

However, when working with objects or arrays, the shallow comparison only checks if the reference to the object has changed. If you create a new object with the same properties, React may not recognize it as a change, leading to potential bugs.

const [user, setUser] = useState({ name: 'Alice' });
// Updating the object with the same values
setUser({ name: 'Alice' }); // This does NOT trigger a re-render

Copying Deep Nested Objects

To ensure that React recognizes changes in deep nested objects, you need to create a deep copy of the object rather than modifying the original object directly. You can achieve this using the spread operator or other methods like JSON.parse(JSON.stringify(...)) for simple objects.

// Updating a nested property
setUser((prevUser) => ({
  ...prevUser,
  details: { ...prevUser.details, age: prevUser.details.age + 1 },
}));

In this example, we create a new user object with an updated age. This ensures that the reference touser changes, triggering a re-render.

Practical Example

Below is an example demonstrating the use of useEffect and the implications of shallow comparison. Click the buttons to toggle the user's name and update the user's age, and see how it affects the rendering.

Count: 0

User: Alice

User Age: 25

Conclusion

Understanding how useEffect and JavaScript's shallow comparison work together is essential for optimizing React performance. By being mindful of how objects and primitives are handled, you can avoid unnecessary re-renders and ensure that your components behave as expected. Keep these concepts in mind as you build your React applications!