useEffect dependency array and ESLint exhaustive-deps rule

30,684

Solution 1

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

Solution 2

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:

Share:
30,684

Related videos on Youtube

Will Jenkins
Author by

Will Jenkins

I've created web apps, mobile apps, distributed micro-service architectures and custom algorithms. I have expertise in analytics, SEO, UX and UI, and can recruit and develop software teams. I believe in high-quality well-crafted maintable code, and strive for ego-free open dialogue at all times.

Updated on July 09, 2022

Comments

  • Will Jenkins
    Will Jenkins almost 2 years

    I have a component that looks like this:

    const MyComponent = props => {
      const { checked, onChange, id } = props;
      const [isChecked, setChecked] = useState(false);
    
      useEffect(() => {
        onChange && onChange({ isChecked: !!checked, id });
        setChecked(checked);
      }, [checked]);
    
      const childProps = {
        id,
        isChecked
      };
    
      return <ChildComponent {...childProps} />;
    };
    

    The exhaustive-deps lint rule isn't happy:

    React Hook useEffect has missing dependencies: id and onChange. Either include them or remove the dependency array. (react-hooks/exhaustive-deps)eslint

    I know that id and onChange are not going to change, so adding them to the dependency array seems unnecessary. But the rule isn't a warning, it's a clear instruction to do something.

    Is the ESLint rule:

    1) Over-cautious and a bit dumb in this instance, so safe to ignore?

    2) Highlighting best practice - i.e. to minimise unexpected bugs that might occur in future if, for instance, changes in parent components mean that id will change at some point in future?

    3) Showing an actual/possible problem with the code as it currently is?

  • Will Jenkins
    Will Jenkins almost 5 years
    Thanks for the links. So it seems that it's more 2), right? i.e. even if you know exactly what you're doing, it will still help eliminate possible bugs and/or make you think about and structure your code carefully.
  • Dupocas
    Dupocas almost 5 years
    Yeap! That's pretty much the gist.
  • Magnus Bull
    Magnus Bull about 3 years
    Side note about your example: If the dispatch comes from a call to useReducer, you can call it in an effect without adding it as a dependency, and ESLint will not complain about it because it is guaranteed to never change. How does it know this? That I would like to know.
  • Dupocas
    Dupocas about 3 years
    yeap. This is a problem addressed in this other answer. Thanks for pointing it out
  • mcv
    mcv about 2 years
    I've come across the advice to use an empty dependency array if you want code to be executed once. Without that, it seems needlessly complicated with extra state to track whether code has been executed. What's the best practice for code you want executed once if an empty array is not allowed?