npm package: @n0n3br/react-use-deep-compare-effect
Description: React hook similar to useEffect but with deep comparison for dependencies.
Author: Rogerio Amorim
Latest version: 1.0.2

# `@n0n3br/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 `@n0n3br/use-deep-compare-effect` using npm, pnpm, or yarn.

using npm
npm install @n0n3br/use-deep-compare-effect

using pnpm
pnpm install @n0n3br/use-deep-compare-effect

using yarn
yarn add @n0n3br/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.