use object in useEffect 2nd param without having to stringify it to JSON
The above answer by @Tholle is absolutely correct. I wrote a post regarding the same on dev.to
In React, side effects can be handled in functional components using useEffect
hook. In this post, I'm going to talk about the dependency array which holds our props/state and specifically what happens in case there's an object in the dependency array.
The useEffect
hook runs even if one element in the dependency array changes. React does this for optimisation purposes. On the other hand, if you pass an empty array then it never re-runs.
However, things become complicated if an object is present in this array. Then even if the object is modified, the hook won't re-run because it doesn't do deep object comparison between these dependency changes for that object. There are couple of ways to solve this problem.
Use lodash's
isEqual
method andusePrevious
hook. This hook internally uses a ref object that holds a mutablecurrent
property that can hold values.It’s possible that in the future React will provide a usePrevious Hook out of the box since it is a relatively common use case.
const prevDeeplyNestedObject = usePrevious(deeplyNestedObject) useEffect(()=>{ if ( !_.isEqual( prevDeeplyNestedObject, deeplyNestedObject, ) ) { // ...execute your code } },[deeplyNestedObject, prevDeeplyNestedObject])
Use
useDeepCompareEffect
hook as a drop-in replacement foruseEffect
hook for objectsimport useDeepCompareEffect from 'use-deep-compare-effect' ... useDeepCompareEffect(()=>{ // ...execute your code }, [deeplyNestedObject])
Use
useCustomCompareEffect
hook which is similar to solution #2
I prepared a CodeSandbox example related to this post. Fork it and check it yourself.
You could create a custom hook that keeps track of the previous dependency array in a ref
and compares the objects with e.g. Lodash isEqual
and only runs the provided function if they are not equal.
Example
const { useState, useEffect, useRef } = React;
const { isEqual } = _;
function useDeepEffect(fn, deps) {
const isFirst = useRef(true);
const prevDeps = useRef(deps);
useEffect(() => {
const isFirstEffect = isFirst.current;
const isSame = prevDeps.current.every((obj, index) =>
isEqual(obj, deps[index])
);
isFirst.current = false;
prevDeps.current = deps;
if (isFirstEffect || !isSame) {
return fn();
}
}, deps);
}
function App() {
const [state, setState] = useState({ foo: "foo" });
useEffect(() => {
setTimeout(() => setState({ foo: "foo" }), 1000);
setTimeout(() => setState({ foo: "bar" }), 2000);
}, []);
useDeepEffect(() => {
console.log("State changed!");
}, [state]);
return <div>{JSON.stringify(state)}</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>