11

Compare the following two components:

Child.js

import React, { useEffect } from "react";

function Child({ count, setCount }) { // Note: Has parameter
  useEffect(() => {
    setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
  }, []);

  return <div>{count}</div>;
}

export default Child;

Child2.js

import React, { useEffect, useState } from "react";

function Child2() { // Note: No parameter
  const [count, setCount] = useState(0); // State variable assigned in component

  useEffect(() => {
    setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
  }, []);

  return <div>{count}</div>;
}

export default Child2;

They are essentially the same. The difference between the two is that Child.js gets the state variable count and its setter setCount passed in from its parent, while Child2.js sets that state variable itself.

They both work fine, but Child.js (and only Child.js) complains about "a missing dependency: 'setCount'." Adding setCount to the dependencies array makes the warning go away, but I'm trying to figure out why that's necessary. Why is the dependency required in Child but not Child2?

I have working examples at https://codesandbox.io/s/react-use-effect-dependencies-z8ukl.

Webucator
  • 2,397
  • 24
  • 39
  • 1
    The setter function coming from the prop could change and become stale, and due to closures, the wrong setter function may be called by the useEffect. The Child2 component doesnt complain because the setter function is declared inside the component, and so the setter function is stable and cannot become stale. This is why Child2 doesn't complain, but Child does. – JMadelaine Apr 03 '20 at 06:42

1 Answers1

11

The ESLint rule is not extremely intelligent to determine what may or maynot change and thus prompts you to pass every variable that is being used within the useEffect function callback so that you don't miss those changes accidently.

Since hooks are heavily dependent on closure, and beginner programmers find it difficult to debug problems related to closures, eslint warning here serves as a good help to avoid such cases.

Now since state updater is directly returned from useState and doesn't change during the course of your component Lifecycle you can avoid passing it as a dependency and disable the warning like

useEffect(() => {
   setInterval(() => {
      setCount(prevCount => prevCount + 1);
   }, 1000);
   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400