reactjs - useEffect dependency array and ESLint exhaustive-deps rule - Stack Overflow
reactjs - Passing array to useEffect dependency list - Stack Overflow
Adding an array in the useEffect dependency array causes an infinte loop
Array in useEffect dependency array
Are you mutating the array? Can you post the code that you’re using to change the array itself?
More on reddit.comCan you use objects or arrays in the useEffect dependency array?
What happens if you don’t pass a dependency array to useEffect?
What is the useEffect hook used for in React?
Videos
When do you need to use it? Why do I need to put a empty array even though its empty? When would I put something in the empty array?
Sorry if stupid noob question.
Old Answer
Actually the rule is very straightforward: Either pass an array containing all dependencies, or don't pass anything. So I guess the rule isn't dumb, it just doesn't know if the dependencies are going to change or not. So yes, if you are passing an array of dependencies it should contain ALL dependencies, including those you know for a fact that will not change. Something like this will throw a warning:
useEffect(() => dispatch({ someAction }), [])
And to fix this you should pass dispatch as a dependency, even though it will never change:
useEffect(() => dispatch({ someAction }), [dispatch])
Don't disable the exhaustive deps rule, as mentioned here
UPDATE 05/04/2021
As addressed here. This is no longer necessary since eslint pull #1950.
Now referential types with stable signature such as those provenients from useState or useDispatch can safely be used inside an effect without triggering exhaustive-deps even when coming from props
The way to look at it is every render has its own effect. If the effect will be the same with a particular set of values, then we can tell React about those values in the dependencies array. Ideally, a component with the same state and props, will always have the same output (rendered component + effect) after its render and effect is done. This is what makes it more resilient to bugs.
The point of the rule is that, if the deps DO change, the effect SHOULD run again, because it is now a different effect.
These 3 links also give more insights about this:
- https://github.com/facebook/react/issues/14920#issuecomment-471070149
- https://overreacted.io/a-complete-guide-to-useeffect/
- https://overreacted.io/writing-resilient-components/
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])
);