We've all been there: you pass an object literal or an array as a dependency to useMemo or useEffect, only to realize it triggers on every single render. Because JavaScript compares objects by reference rather than structure, {} never equals {}.
In the latest release of react-hook-lab (v1.6.0), we are introducing a highly requested utility to put an end to referential instability: useDeepMemo, alongside a fully-featured, production-ready deepEqual helper.
The Problem: Strict Equality vs. Structural Equality
React's native useMemo uses strict reference equality (Object.is) to check if dependencies have changed. If your parent component passes a configuration object down, or if you define an array literal inline, React assumes the dependency has changed, forcing expensive recalculations.
The Solution: useDeepMemo
Our new useDeepMemo hook compares dependencies by structural value rather than reference. It uses a custom deep equality engine that handles complex edge cases—including circular references, nested objects, arrays, Dates, RegExps, Maps, and Sets.
Let's look at how you can use these new updates in your React projects.
Code Example 1: Preventing Re-calculations with useDeepMemo
In this example, filters is an object recreation on every render. Standard useMemo would fail to optimize this, but useDeepMemo handles it gracefully.
import React, { useState } from 'react';
import { useDeepMemo } from 'react-hook-lab';
interface FilterProps {
category: string;
tags: string[];
}
export const ProductDashboard = ({ category, tags }: FilterProps) => {
const [search, setSearch] = useState('');
// This object literal is recreated on every render
const filterQuery = { category, tags };
// useDeepMemo ensures this only recalculates when the inner values change!
const processedResults = useDeepMemo(() => {
console.log('Running expensive filter operation...');
return performHeavySearch(filterQuery, search);
}, [filterQuery, search]);
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)} />
{/* Render results */}
</div>
);
};
function performHeavySearch(query: FilterProps, search: string) {
return [`Result for ${query.category} with search: ${search}`];
}
Code Example 2: Streamlined State with the Updated useToggle
We have also refactored the useToggle hook to support cleaner object destructuring. Instead of matching index positions in an array, you can now grab value and toggle directly.
import React from 'react';
import { useToggle } from 'react-hook-lab';
export const ThemeSwitcher = () => {
// useToggle now returns an intuitive object representation!
const { value: theme, toggle: toggleTheme } = useToggle('light', 'dark');
return (
<div className={`app-${theme}`}>
<p>Active Theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};
Under the Hood: Deep Equality Utilities
The engine powering this hook is our standalone deepEqual utility. It was migrated and exposed globally so you can use it anywhere in your codebase. It safely handles:
-
Circular References: Uses a
WeakMaptracker to prevent infinite recursion. -
Built-in Types: Deep-compares
Datetimestamps,RegExpsources, andMapvalues. - Scalable Set Handling: Safely matches sets with scalable reference tracking.
We have also updated the documentation for useBoolean to align with the correct api methods (setTrue and setFalse).
Resources
- GitHub Repository: Saurav-TB-Pandey/react-hook-lab
- NPM Package: react-hook-lab on npm
- Connect with me: LinkedIn Profile
Originally published on my blog. You can read the alternative breakdown here.
Top comments (0)