useDeepCompareEffect
Works just like useEffect but with objects being compared by value not reference.
Installation
npx rabbithook@latest add use-deep-compare-effect
Usage
import useDeepCompareEffect from "@/hooks/use-deep-compare-effect";
function Component() { const [user, setUser] = useState({ id: "any_id", name: "any_name", address: { street: "any_street", city: "any_city", country: "any_country" } }); const [count, setCount] = useState(0);
useEffect(() => { console.log("Normal effect called with user: ", user); }, [user]) // Will be called every time count changes
useDeepCompareEffect(() => { console.log("Deep compared effect called with user: ", user); }, [user]) // Will be called only when user changes
return ( <> <button onClick={() => setCount(prevCount => prevCount++)}>Increase Counter</button> <button onClick={() => setUser(prevUser => ({...prevUser, name: "new_name" }))}> Update user </button> </> )}
Code
import { useRef, useEffect, EffectCallback } from "react"
type ICompareValue = Record<string, unknown>;type ICompare= ICompareValue[] | ICompareValue;
function useDeepCompareEffect(callback: EffectCallback, dependencies: unknown[]) { const currentDependenciesRef = useRef<unknown[]>([]);
if (!deepCompare(currentDependenciesRef.current as ICompare, dependencies as ICompare)) { currentDependenciesRef.current = dependencies; }
useEffect(callback, [currentDependenciesRef.current])}
const deepCompare = (firstValue: ICompare, secondValue: ICompare) => { if (!isObject(firstValue) || !isObject(secondValue)) return firstValue === secondValue;
const keys1 = Object.keys(firstValue); const keys2 = Object.keys(secondValue);
if (keys1.length !== keys2.length) return false;
let areObjectsEqual = true;
keys1.forEach(key => { const valueInObject1 = firstValue[key]; const valueInObject2 = secondValue[key];
const areObjects = isObject(valueInObject1) && isObject(valueInObject2);
const areEqual = areObjects ? deepCompare(valueInObject1 as ICompareValue, valueInObject2 as ICompareValue) : valueInObject1 === valueInObject2;
if (areEqual) { areObjectsEqual = false; } })
return areObjectsEqual;}
const isObject = (value: any) => value !== null && typeof value === "object";
export default useDeepCompareEffect;
import { useRef, useEffect } from "react"
function useDeepCompareEffect(callback, dependencies) { const currentDependenciesRef = useRef([]);
if (!deepCompare(currentDependenciesRef.current, dependencies)) { currentDependenciesRef.current = dependencies; }
useEffect(callback, [currentDependenciesRef.current])}
const deepCompare = (firstValue, secondValue) => { if (!isObject(firstValue) || !isObject(secondValue)) return firstValue === secondValue;
const keys1 = Object.keys(firstValue); const keys2 = Object.keys(secondValue);
if (keys1.length !== keys2.length) return false;
let areObjectsEqual = true;
keys1.forEach(key => { const valueInObject1 = firstValue[key]; const valueInObject2 = secondValue[key];
const areObjects = isObject(valueInObject1) && isObject(valueInObject2);
const areEqual = areObjects ? deepCompare(valueInObject1, valueInObject2) : valueInObject1 === valueInObject2;
if (areEqual) { areObjectsEqual = false; } })
return areObjectsEqual;}
const isObject = (value) => value !== null && typeof value === "object";
export default useDeepCompareEffect;