@n0n3br/react-use-deep-compare-effect
Version:
React hook similar to useEffect but with deep comparison for dependencies.
167 lines (129 loc) • 5.66 kB
Markdown
# `/react-use-deep-compare`
A React hook that works just like `useEffect`, but performs a deep comparison of its dependencies, preventing unnecessary re-runs when object or array references change but their content remains the same.
## 💡 Why `useDeepCompareEffect`?
React's built-in `useEffect` hook performs a **shallow comparison** of its dependencies. This means if you pass an object or an array as a dependency, `useEffect` will re-run the effect every time the object/array reference changes, even if its internal content is identical.
This can lead to:
- **Unnecessary re-renders and computations:** Your effect might run more often than needed, impacting performance.
- **Infinite loops:** If your effect updates state that causes a new object/array reference to be created, and that object/array is a dependency, you can easily end up in a loop.
`useDeepCompareEffect` solves this by performing a **deep comparison** of your dependencies. It only re-runs your effect when the actual _content_ of your object or array dependencies changes, not just their reference.
## ✨ Features
- **Deep Equality Check:** Compares the actual values of objects and arrays, not just their references.
- **Circular Reference Handling:** Safely handles objects with circular references to prevent infinite loops during comparison.
- **Specific Type Handling:** Correctly compares `Date` objects by their time value and `RegExp` objects by their source and flags.
- **Function Reference Comparison:** Like `useEffect`, functions are compared by reference, ensuring standard React behavior for callbacks.
- **Type-Safe:** Built with TypeScript for a robust development experience.
## 🚀 Installation
You can install `/use-deep-compare-effect` using npm, pnpm, or yarn.
using npm
npm install /use-deep-compare-effect
using pnpm
pnpm install /use-deep-compare-effect
using yarn
yarn add /use-deep-compare-effect
## 📖 Usage
Using `useDeepCompareEffect` is almost identical to using `useEffect`. Just import it and use it in place of `useEffect`.
```typescript jsx
import React, { useState } from "react";
import { useDeepCompareEffect } from "use-deep-compare-effect"; // Adjust path if not installed as a package
function MyComponent() {
const [settings, setSettings] = useState({
theme: "dark",
notifications: {
email: true,
sms: false,
},
tags: ["react", "hook"],
});
const [effectLog, setEffectLog] = useState<string[]>([]);
// This effect will only run when the DEEP content of 'settings' changes.
useDeepCompareEffect(() => {
const timestamp = new Date().toLocaleTimeString();
setEffectLog((prev) => [
...prev,
`useDeepCompareEffect ran at: ${timestamp} (Settings changed deeply)`,
]);
console.log("Deeply compared effect ran:", settings);
// Optional cleanup function
return () => {
console.log("Deeply compared effect cleanup");
};
}, [settings]); // Dependencies are objects/arrays that need deep comparison
// --- For comparison: A standard useEffect ---
// This effect would run every time 'settings' reference changes,
// even if its content is the same.
// useEffect(() => {
// console.log('Standard useEffect ran:', settings);
// }, [settings]);
const updateTheme = () => {
setSettings((prev) => ({
...prev,
theme: prev.theme === "dark" ? "light" : "dark",
}));
};
const toggleEmailNotifications = () => {
setSettings((prev) => ({
...prev,
notifications: {
...prev.notifications,
email: !prev.notifications.email,
},
}));
};
const addTag = () => {
setSettings((prev) => ({
...prev,
tags: [...prev.tags, `new-tag-${prev.tags.length + 1}`],
}));
};
const triggerShallowUpdate = () => {
// This creates a NEW settings object reference, but with the SAME DEEP content
// useDeepCompareEffect WILL NOT run.
// Standard useEffect WOULD run.
setSettings((prev) => ({ ...prev }));
};
return (
<div
style={{
padding: "20px",
border: "1px solid #ccc",
borderRadius: "8px",
}}>
<h2>My Component with Deep Compare Effect</h2>
<pre>{JSON.stringify(settings, null, 2)}</pre>
<div style={{ display: "flex", gap: "10px", marginTop: "10px" }}>
<button onClick={updateTheme}>Update Theme (Deep Change)</button>
<button onClick={toggleEmailNotifications}>
Toggle Email Notif. (Deep Change)
</button>
<button onClick={addTag}>Add Tag (Deep Change)</button>
<button onClick={triggerShallowUpdate}>
Trigger Shallow Update (No Deep Change)
</button>
</div>
<h3>Effect Log:</h3>
<div
style={{
border: "1px solid #eee",
padding: "10px",
maxHeight: "150px",
overflowY: "auto",
}}>
{effectLog.length === 0 ? (
<p>No effect runs yet...</p>
) : (
<ul>
{effectLog.map((log, index) => (
<li key={index}>{log}</li>
))}
</ul>
)}
</div>
</div>
);
}
export default MyComponent;
```
## 🤝 Contributing
Contributions are welcome! If you find a bug or have an idea for an enhancement, please open an issue or submit a pull request.
## 📄 License
This project is licensed under the MIT License - see the LICENSE file for details.