Passing array to useEffect dependency list
You can pass JSON.stringify(outcomes)
as the dependency list:
Read more here
useEffect(() => {
console.log(outcomes)
}, [JSON.stringify(outcomes)])
Using JSON.stringify()
or any deep comparison methods may be inefficient, if you know ahead the shape of the object, you can write your own effect hook that triggers the callback based on the result of your custom equality function.
useEffect
works by checking if each value in the dependency array is the same instance with the one in the previous render and executes the callback if one of them is not. So we just need to keep the instance of the data we're interested in using useRef
and only assign a new one if the custom equality check return false
to trigger the effect.
function arrayEqual(a1: any[], a2: any[]) {
if (a1.length !== a2.length) return false;
for (let i = 0; i < a1.length; i++) {
if (a1[i] !== a2[i]) {
return false;
}
}
return true;
}
type MaybeCleanUpFn = void | (() => void);
function useNumberArrayEffect(cb: () => MaybeCleanUpFn, deps: number[]) {
const ref = useRef<number[]>(deps);
if (!arrayEqual(deps, ref.current)) {
ref.current = deps;
}
useEffect(cb, [ref.current]);
}
Usage
function Child({ arr }: { arr: number[] }) {
useNumberArrayEffect(() => {
console.log("run effect", JSON.stringify(arr));
}, arr);
return <pre>{JSON.stringify(arr)}</pre>;
}
Taking one step further, we can also reuse the hook by creating an effect hook that accepts a custom equality function.
type MaybeCleanUpFn = void | (() => void);
type EqualityFn = (a: DependencyList, b: DependencyList) => boolean;
function useCustomEffect(
cb: () => MaybeCleanUpFn,
deps: DependencyList,
equal?: EqualityFn
) {
const ref = useRef<DependencyList>(deps);
if (!equal || !equal(deps, ref.current)) {
ref.current = deps;
}
useEffect(cb, [ref.current]);
}
Usage
useCustomEffect(
() => {
console.log("run custom effect", JSON.stringify(arr));
},
[arr],
(a, b) => arrayEqual(a[0], b[0])
);
Live Demo
As an addendum to loi-nguyen-huynh's answer, for anyone encountering the eslint exhaustive-deps warning, this can be resolved by first breaking the stringified JSON out into a variable:
const depsString = JSON.stringify(deps);
React.useEffect(() => {
...
}, [depsString]);
Another ES6 option would be to use template literals to make it a string. Similar to JSON.stringify()
, except the result won't be wrapped in []
useEffect(() => {
console.log(outcomes)
}, [`${outcomes}`])
Another option, if the array size doesn't change, would be to spread it in:
useEffect(() => {
console.log(outcomes)
}, [ ...outcomes ])