← Back to all hooks

useDeepCompareEffect

Like useEffect, but with deep comparison of dependencies. Prevents infinite re-renders when using objects or arrays as dependencies.

Live Demo

Compare regular useEffect vs useDeepCompareEffect. Notice how regular useEffect fires on every render, while useDeepCompareEffect only fires when values actually change.

📊 Effect Execution Count

Regular useEffect:0
useDeepCompareEffect:0
Efficiency Gain: 0% fewer executions

📝 Event Log

No events yet

👤 User Object

{ "name": "John", "age": 25 }

💡 Notice:

  • Regular useEffect fires on every render (even with same values)
  • useDeepCompareEffect only fires when object content actually changes
  • Click "Trigger Re-render" to see the difference
  • This prevents infinite loops with object/array dependencies

Code Examples

The Problem with Regular useEffect

import { useEffect, useState } from "react";

// ❌ This will cause infinite re-renders!
export default function BadExample() {
  const [data, setData] = useState([]);
  
  useEffect(() => {
    // This creates a new array reference every time
    const filters = { status: 'active', limit: 10 };
    
    fetchData(filters).then(setData);
  }, [{ status: 'active', limit: 10 }]); // New object every render!

  return <div>{data.length} items</div>;
}

Solution with useDeepCompareEffect

import { useDeepCompareEffect } from "@uiblock/hooks";
import { useState } from "react";

// ✅ This works correctly!
export default function GoodExample() {
  const [data, setData] = useState([]);
  
  useDeepCompareEffect(() => {
    const filters = { status: 'active', limit: 10 };
    
    fetchData(filters).then(setData);
  }, [{ status: 'active', limit: 10 }]); // Deep comparison!

  return <div>{data.length} items</div>;
}

Fetching Data with Complex Filters

import { useDeepCompareEffect } from "@uiblock/hooks";
import { useState } from "react";

interface Filters {
  search: string;
  categories: string[];
  priceRange: { min: number; max: number };
}

export default function ProductList() {
  const [products, setProducts] = useState([]);
  const [filters, setFilters] = useState<Filters>({
    search: '',
    categories: ['electronics'],
    priceRange: { min: 0, max: 1000 }
  });

  useDeepCompareEffect(() => {
    fetchProducts(filters).then(setProducts);
  }, [filters]);

  return (
    <div>
      <input
        value={filters.search}
        onChange={(e) => setFilters({ ...filters, search: e.target.value })}
      />
      {/* Products list */}
    </div>
  );
}

Array Dependencies

import { useDeepCompareEffect } from "@uiblock/hooks";
import { useState } from "react";

export default function SelectedItems() {
  const [selectedIds, setSelectedIds] = useState([1, 2, 3]);
  const [items, setItems] = useState([]);

  useDeepCompareEffect(() => {
    // Only fetches when selectedIds content actually changes
    fetchItemsByIds(selectedIds).then(setItems);
  }, [selectedIds]);

  return <div>{items.length} selected items</div>;
}

How It Works

Here's the implementation:

import { useEffect, useRef, DependencyList, EffectCallback } from 'react'

function deepEqual(a: any, b: any): boolean {
  if (a === b) return true
  if (a == null || b == null) return false
  if (typeof a !== 'object' || typeof b !== 'object') return false

  const keysA = Object.keys(a)
  const keysB = Object.keys(b)

  if (keysA.length !== keysB.length) return false

  for (const key of keysA) {
    if (!keysB.includes(key)) return false
    if (!deepEqual(a[key], b[key])) return false
  }

  return true
}

function useDeepCompareMemoize(value: DependencyList) {
  const ref = useRef<DependencyList>([])

  if (!deepEqual(value, ref.current)) {
    ref.current = value
  }

  return ref.current
}

export function useDeepCompareEffect(
  effect: EffectCallback,
  dependencies: DependencyList
) {
  useEffect(effect, useDeepCompareMemoize(dependencies))
}

Key Features:

  • Recursively compares object and array contents
  • Only updates dependencies when content actually changes
  • Prevents infinite re-render loops
  • Works with nested objects and arrays

When to Use

✅ Good Use Cases

  • Object or array dependencies
  • Complex filter objects
  • Configuration objects
  • API request parameters

❌ Avoid When

  • Dependencies are primitives
  • Very large/deep objects
  • High-frequency updates
  • Can use useMemo instead

API Reference

Parameters

effect: EffectCallback

The effect function to run (same as useEffect)

dependencies: DependencyList

Array of dependencies (will be deeply compared)

Returns

void - Works exactly like useEffect

⚠️ Performance Note

Deep comparison has a performance cost. For very large or deeply nested objects, consider:

  • Using useMemo to stabilize object references
  • Breaking down into smaller, primitive dependencies
  • Using a library like lodash.isEqual for more optimized comparison