1.0.2 • Published 10 months ago

@n0n3br/react-use-deep-compare-effect v1.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

@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.

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.

1.0.2

10 months ago

1.0.1

10 months ago

1.0.0

10 months ago