I trying to wrap my head around the new hooks api of react. Specifically, I'm trying to construct the classic use case that once was the following:
componentDidUpdate(prevProps) { if (prevProps.foo !== this.props.foo) { // animate dom elements here... this.animateSomething(this.ref, this.props.onAnimationComplete); } }
Now, I tried to build the same with a function component and useEffect
, but can't figure out how to do it. This is what I tried:
useEffect(() => { animateSomething(ref, props.onAnimationComplete); }, [props.foo]);
This way, the effect is only called when props.foo changes. And that does work – BUT! It appears to be an anti-pattern since the eslint-plugin-react-hooks
marks this as an error. All dependencies that are used inside the effect should be declared in the dependencies array. So that means I would have to do the following:
useEffect(() => { animateSomething(ref, props.onAnimationComplete); }, [props.foo, ref, props.onAnimationComplete]);
That does not lead to the linting error BUT it totally defeats the purpose of only calling the effect when props.foo
changes. I don't WANT it to be called when the other props or the ref change.
Now, I read something about using useCallback
to wrap this. I tried it but didn't get any further.
Can somebody help?
This array will re-run useEffect, if the values inside it changes. This will work perfectly fine when the values passed in the dependency array are of type boolean, string or numbers. But it will have some gotchas when you are dealing with complex values such as objects or arrays.
useEffect(callback, dependencies) is the hook that manages the side-effects in functional components. callback argument is a function to put the side-effect logic. dependencies is a list of dependencies of your side-effect: being props or state values.
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we'll refer to it as our “effect”), and call it later after performing the DOM updates.
The warning "React Hook useEffect has a missing dependency" occurs when the useEffect hook makes use of a variable or function that we haven't included in its dependencies array. To solve the error, disable the rule for a line or move the variable inside the useEffect hook.
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]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With