An effect that re-runs whenever an unrelated value changes is usually reading something it does not need. A timer is the clearest case: setCount(count + 1) needs count, so count must be a dependency, so React 7,897 clears and recreates the interval on every tick. The updater form needs no dependency at all.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('subscribing'); // logged once, not once per second
const id = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(id);
}, []); // reads no reactive value
return <h1>{count}</h1>;
}subscribing
Three moves remove most spurious dependencies:
Use an updater function when the effect only needs the previous state, as above.
Move a function inside the effect. A function declared in the component body is a new value on every render, so listing it re-runs the effect every render. Declared inside, it is not a dependency.
Move a constant out of the component. An object or array literal in the body (const opts = {a: 1}) also changes identity every render; hoist it to module scope, or build it inside the effect.
Deleting a dependency the linter asked for never works: the effect then fires with values from an old render, and the bug moves from "too often" to "silently wrong". Separating Events from Effects has the supported way to say that a value is not reactive.