UNPKG

@n0n3br/react-use-deep-compare-effect

Version:

React hook similar to useEffect but with deep comparison for dependencies.

149 lines (148 loc) 6.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useDeepCompareEffect = exports.areDepsEqualManually = exports.isDeepEqual = void 0; var react_1 = require("react"); // Internal helper function for deep comparison. // Handles primitives, objects, arrays, Date, RegExp, and circular references. function _isDeepEqualInternal(val1, val2, memo // Used to detect and handle circular references ) { // 1. Identity and primitive comparison (Object.is handles NaN correctly) if (Object.is(val1, val2)) { return true; } // 2. If types are different or one of them is null (but not both, already handled by Object.is) // or if they are not objects (meaning they are different primitives, since Object.is failed) if (typeof val1 !== typeof val2 || val1 === null || val2 === null || (typeof val1 !== "object" && typeof val1 !== "function") || // Ensures both are objects/functions to proceed (typeof val2 !== "object" && typeof val2 !== "function")) { return false; } // From here, val1 and val2 are non-identical objects or functions. // Functions are handled by reference by the `areDepsEqualManually` logic, // but if `isDeepEqual` is called directly with functions, `Object.is` already handled them. // 3. Cycle detection: if we've already compared val1 and it was paired with val2, it's an equal cycle. if (memo.has(val1) && memo.get(val1) === val2) { return true; } // Add the pair to the memo before recursion. // We use `object` as the key type for WeakMap, as only objects can be keys. if (typeof val1 === "object" && typeof val2 === "object") { memo.set(val1, val2); } // 4. Specific object types if (val1 instanceof Date && val2 instanceof Date) { return val1.getTime() === val2.getTime(); } if (val1 instanceof RegExp && val2 instanceof RegExp) { return val1.source === val2.source && val1.flags === val2.flags; } // If one is Date/RegExp and the other isn't (and they are not identical by reference, already checked) if (val1 instanceof Date !== val2 instanceof Date || val1 instanceof RegExp !== val2 instanceof RegExp) { return false; } // 5. Arrays if (Array.isArray(val1) && Array.isArray(val2)) { if (val1.length !== val2.length) return false; for (var i = 0; i < val1.length; i++) { if (!_isDeepEqualInternal(val1[i], val2[i], memo)) return false; } return true; } // If one is an array and the other isn't (and not identical by reference) if (Array.isArray(val1) !== Array.isArray(val2)) return false; // 6. Objects (plain objects or other complex objects not handled above) // Check if both are generic objects (not arrays, not null) var isVal1Object = typeof val1 === "object" && val1 !== null; var isVal2Object = typeof val2 === "object" && val2 !== null; if (isVal1Object && isVal2Object) { var keys1 = Object.keys(val1); var keys2 = Object.keys(val2); if (keys1.length !== keys2.length) return false; for (var _i = 0, keys1_1 = keys1; _i < keys1_1.length; _i++) { var key = keys1_1[_i]; if (!Object.prototype.hasOwnProperty.call(val2, key)) return false; if (!_isDeepEqualInternal(val1[key], val2[key], memo)) { return false; } } return true; } // If it reached here, types are different or not deeply comparable as expected // (e.g., a function and an object, or types not explicitly covered) return false; } /** * Performs a deep comparison between two values. * @param val1 The first value. * @param val2 The second value. * @returns `true` if the values are deeply equal, `false` otherwise. */ function isDeepEqual(val1, val2) { return _isDeepEqualInternal(val1, val2, new WeakMap()); } exports.isDeepEqual = isDeepEqual; /** * Compares two dependency arrays, using deep comparison for objects/arrays * and reference comparison for functions. */ function areDepsEqualManually(// Exporting for testing purposes prevDeps, nextDeps) { if (prevDeps === undefined) { // First render or no previous dependencies return false; } if (prevDeps.length !== nextDeps.length) { return false; } for (var i = 0; i < prevDeps.length; i++) { var prevDep = prevDeps[i]; var nextDep = nextDeps[i]; // Rule: Functions are compared by reference if (typeof prevDep === "function" && typeof nextDep === "function") { if (prevDep !== nextDep) { return false; } continue; // Move to the next dependency } // For other types, use deep comparison if (!isDeepEqual(prevDep, nextDep)) { return false; } } return true; } exports.areDepsEqualManually = areDepsEqualManually; /** * React hook that behaves like `useEffect`, but performs a deep comparison * of dependencies instead of a shallow reference comparison. * * @param callback Function to be executed when dependencies change. * Can return a cleanup function. * @param dependencies Dependency array. The callback is executed if any * dependency changes deeply (except functions, which are * compared by reference). */ function useDeepCompareEffect(callback, dependencies) { var prevDependenciesRef = (0, react_1.useRef)(undefined); var signalRef = (0, react_1.useRef)(0); // A value that changes to trigger useEffect // Check if dependencies have actually changed through deep comparison. // This check is done here, outside useEffect, to decide whether to update // prevDependenciesRef and signalRef. if (!areDepsEqualManually(prevDependenciesRef.current, dependencies)) { prevDependenciesRef.current = dependencies; // Store the new dependencies signalRef.current++; // Change the "signal" to trigger the effect } // The native useEffect now only depends on `signalRef.current`. // It will be executed when `signalRef.current` changes, // which only happens when our deep comparison logic indicates a real change. (0, react_1.useEffect)(callback, [signalRef.current]); } exports.useDeepCompareEffect = useDeepCompareEffect;