You can pass JSON.stringify(outcomes) as the dependency list:
Read more here
useEffect(() => {
console.log(outcomes)
}, [JSON.stringify(outcomes)])
Answer from Loi Nguyen Huynh on Stack OverflowYou 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
Can someone explain useEffect dependency arrays like im 5?
reactjs - In useEffect, what's the difference between providing no dependency array and an empty one? - Stack Overflow
reactjs - How to manage dependency arrays with useEffect - Stack Overflow
reactjs - useEffect and dependency array - Stack Overflow
Why is the dependency array important in useEffect?
Can you use objects or arrays in the useEffect dependency array?
What happens if you don’t pass a dependency array to useEffect?
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.
It's not quite the same.
Giving it an empty array acts like
componentDidMountas in, it only runs once.Giving it no second argument acts as both
componentDidMountandcomponentDidUpdate, as in it runs first on mount and then on every re-render.Giving it an array as second argument with any value inside, eg
, [variable1]will only execute the code inside youruseEffecthook ONCE on mount, as well as whenever that particular variable (variable1) changes.
You can read more about the second argument as well as more on how hooks actually work on the official docs at https://reactjs.org/docs/hooks-effect.html
Just an addition to @bamtheboozle's answer.
If you return a clean up function from your useEffect
useEffect(() => {
performSideEffect();
return cleanUpFunction;
}, []);
It will run before every useEffect code run, to clean up for the previous useEffect run. (Except the very first useEffect run)
One of the rules of React hooks is that useEffect must include all variables that it references in its dependency array. That's why you're getting the error. You can have additional variables, like varB, but you cannot exclude one, otherwise you could run into some wonky behavior.
A way around it is to use a ref, which allows you to store the value in a way that never triggers a render. The ref will need to be included in the dependency array, but since it never changes it won't trigger the useEffect again.
const varARef = useRef(varA)
useEffect(()=> {
DO SOMETHING TO varARef.current;
}, [varARef, varB])
Note that if you do want varARef.current to stay in sync with varA, you'll want to add a line for that:
const varARef = useRef(varA)
varARef.current = varA
useEffect(()=> {
DO SOMETHING TO varARef.current;
}, [varARef, varB])
If you have to update the state of a variable using its current state the best way to do it is using a callback inside setState.
If you change your code like this no issue will be triggered.
[varB, setVarB] = useState("");
[counter, setCounter] = useState(0);
useEffect(()=>{
setCounter(currentCounter => currentCounter+1);
},[varB]) // No warning here
Full answer: useEffect has a missing dependency when updating state based on current value
useEffect will re-run with an object in it's dependency array whenever that object changes references.
For example in your code, user is always stable until you call setUser in which case it will change to a new reference and run at that point.
If you instead defined user as a new object in each render, then the useEffect would instead run every time the component re-rendered.
const user = {
firstName: 'Joe',
lastName: 'Smith',
address: {
home: '123 street',
},
};
It's all about referential equality. Whether previousUser===newUser on each render. React essentially performs a check very similar to that (I believe Object.is) for every variable in the dependency array.
Bellow your component.
changes:
- move
initialStateoutside UseEffectHook. You don't need to createinitialStateevery re-render; - use useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed;
setUserandsetCountwith updater function;handleOnChangeUserable to use for all fields of user object. Every time handleOnChangeUser - create new ref to user object. in this case useEffect able to detect changes;
import React, { useCallback, useEffect, useState } from 'react';
const initialState = {
firstName: "Joe",
lastName: "Smith",
address: {
home: "123 street",
}
};
export function UseEffectHook() {
const [user, setUser] = useState(initialState);
const [count, setCount] = useState(0);
const handleOnChangeUser = useCallback((event) => {
const { name, value } = event;
setUser(prevState => ({ ...prevState, [name]: value }));
}, []);
const increaseCount = useCallback(() => {
setCount(prevState => prevState + 1);
}, []);
useEffect(() => {
console.log("triggered");
}, [user]);
return (
<div>
<p>useEffect - Practice with different deps</p>
{JSON.stringify(user)}
<label>
<span>Firstname:</span>
<input
type="text"
name="firstName"
onChange={handleOnChangeUser}
/>
</label>
<br />
<label>
<span>Lastname:</span>
<input
type="text"
name="lastName"
onChange={handleOnChangeUser}
/>
</label>
<button onClick={increaseCount}>Increase</button>
</div>
);
}
I would recommend writing this as follows:
const previousFooRef = useRef(props.foo);
useEffect(() => {
if (previousFooRef.current !== props.foo) {
animateSomething(ref, props.onAnimationComplete);
previousFooRef.current = props.foo;
}
}, [props.foo, props.onAnimationComplete]);
You can't avoid the complexity of having a condition inside the effect, because without it you will run your animation on mount rather than just when props.foo changes. The condition also allows you to avoid animating when things other than props.foo change.
By including props.onAnimationComplete in the dependencies array, you avoid disabling the lint rule which helps ensure that you don’t introduce future bugs related to missing dependencies.
Here's a working example:
Suppress the linter because it gives you a bad advice. React requires you to pass to the second argument the values which (and only which) changes must trigger an effect fire.
useEffect(() => {
animateSomething(ref, props.onAnimationComplete);
}, [props.foo]); // eslint-disable-line react-hooks/exhaustive-deps
It leads to the same result as the Ryan's solution.
I see no problems with violating this linter rule. In contrast to useCallback and useMemo, it won't lead to errors in common case. The content of the second argument is a high level logic.
You may even want to call an effect when an extraneous value changes:
useEffect(() => {
alert(`Hi ${props.name}, your score is changed`);
}, [props.score]);