React hook missing dependency of custom hook setter

Viewed 400

I'm well aware of what the Hook has missing dependency is, what it means and why it's important to watch all dependencies, but this one is just weird.

export function Compo() {
  const [value, setValue] = useState<number>();

  useEffect(() => {
    setValue(Date.now());
  }, []);

  return (
    <>{value}</>
  );
}

works fine, but:

function useValue() {
  return useState<number>();
}

export function Compo() {
  const [value, setValue] = useValue();

  useEffect(() => {
    setValue(Date.now());
  }, []);

  return (
    <>{value}</>
  );
}

show the well known React Hook useEffect has a missing dependency: 'setValue'. Either include it or remove the dependency array react-hooks/exhaustive-deps.

1 Answers

What you've noticed in your example is a quirk of the rule react-hooks/exhaustive-deps. It gives special privilege to hooks it is aware of, and knows to be "stable" under certain circumstances.

Quoting the implementation:

// Next we'll define a few helpers that helps us
// tell if some values don't have to be declared as deps.

// Some are known to be stable based on Hook calls.
// const [state, setState] = useState() / React.useState()
//               ^^^ true for this reference
// const [state, dispatch] = useReducer() / React.useReducer()
//               ^^^ true for this reference
// const ref = useRef()
//       ^^^ true for this reference
// False for everything else.

source: https://github.com/facebook/react/blob/v17.0.1/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L152

Specifically, this part of the rule seems to be what is exempting the useState hook's setter under these circumstances:

if (name === 'useState') {
  const references = resolved.references;
  for (let i = 0; i < references.length; i++) {
    setStateCallSites.set(
      references[i].identifier,
      id.elements[0],
    );
  }
}
// Setter is stable.
return true;

The unfortunate result of the hook being helpfuln/clever is that it can lead to confusion where its inference doesn't work, like the scenario you just described.

Related