An enhanced version of the useEffect hook with deep comparison on the dependencies.
import { useEffect, useRef, useMemo } from "react";
import deepEqual from "fast-deep-equal/react";
interface DeepEffectHook {
(...args: Parameters<typeof useEffect>): ReturnType<typeof useEffect>;
}
/**
* react `useEffect` hook with fast and deep comparison on its dependencies.
*
* ⚠️ Usage with **no** dependencies or **primitive** ones is not allowed
*/
const useDeepEffect: DeepEffectHook = function useDeepEffect(
callback,
dependencies
) {
const ref = useRef(dependencies);
const signalRef = useRef(0);
if (!deepEqual(dependencies, ref.current)) {
ref.current = dependencies;
signalRef.current += 1;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const deps = useMemo(() => ref.current, [signalRef.current]);
// eslint-disable-next-line react-hooks/exhaustive-deps
return useEffect(callback, deps);
};
const [state, setState] = useState<{ a?: unknown; b?: unknown }>({
a: null,
b: null,
});
useDeepEffect(() => {
// do stuff
}, [state]);
import { renderHook } from "@testing-library/react";
import useDeepEffect from "@hooks/useDeepEffect";
test("useDeepEffect should handle changing values as expected", () => {
const callback = jest.fn();
let deps = [{ a: "b" }];
const { rerender } = renderHook(() => useDeepEffect(callback, deps));
expect(callback).toHaveBeenCalledTimes(1);
deps = [{ a: "b" }];
rerender();
expect(callback).toHaveBeenCalledTimes(1);
deps = [{ a: "c" }];
rerender();
expect(callback).toHaveBeenCalledTimes(2);
});