<h2 dir="auto">Context</h2>
<p dir="auto">After reading <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="413077280" data-permission-text="Title is private" data-url="https://github.com/facebook/react/issues/14920" data-hovercard-type="issue" data-hovercard-url="/facebook/react/issues/14920/hovercard" href="https://github.com/facebook/react/issues/14920">#14920</a> (not 100% of it, I admit), I decided to refactor my codebase in order to be more resilient against potential mistakes.<br>
During this conversion, I converted a ton of callbacks to <code class="notranslate">useCallback</code> and made extensive use of the array dependencies for <code class="notranslate">useCallback</code> and <code class="notranslate">useEffect</code> everywhere.<br>
However, I often stumbled upon the following pattern:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="const myCallback = useCallback(() =&gt; {
  // Do something with value.
}, [value]);

const myOtherCallback = useCallback(() =&gt; {
  const result = myCallback();

  // Do something with BOTH value and result.
}, [value, myCallback ]);

useEffect(() =&gt; {
  // Do something with myCallback, myOtherCallback and value, independently.
}, [value, myCallback, myOtherCallback ])"><pre class="notranslate"><span class="pl-k">const</span> <span class="pl-s1">myCallback</span> <span class="pl-c1">=</span> <span class="pl-en">useCallback</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-c">// Do something with value.</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-s1">value</span><span class="pl-kos">]</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-k">const</span> <span class="pl-s1">myOtherCallback</span> <span class="pl-c1">=</span> <span class="pl-en">useCallback</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-k">const</span> <span class="pl-s1">result</span> <span class="pl-c1">=</span> <span class="pl-s1">myCallback</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-c">// Do something with BOTH value and result.</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-s1">value</span><span class="pl-kos">,</span> <span class="pl-s1">myCallback</span> <span class="pl-kos">]</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-en">useEffect</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-c">// Do something with myCallback, myOtherCallback and value, independently.</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-s1">value</span><span class="pl-kos">,</span> <span class="pl-s1">myCallback</span><span class="pl-kos">,</span> <span class="pl-s1">myOtherCallback</span> <span class="pl-kos">]</span><span class="pl-kos">)</span></pre></div>
<p dir="auto"><a href="https://codesandbox.io/s/goofy-thunder-2y4s9" rel="nofollow">Basic CodeSandBox with this example</a></p>
<p dir="auto">For the sake of argument, let's say that this code can't "move" callbacks around: I can't declare <code class="notranslate">myCallback</code> and/or <code class="notranslate">myOtherCallback</code> because they are used below in the main scope.</p>
<h2 dir="auto">Problem</h2>
<p dir="auto">With the above example, we can conclude the following statements:</p>
<ul dir="auto">
<li><code class="notranslate">myCallback</code> will change if <code class="notranslate">value</code> changes</li>
<li><code class="notranslate">myOtherCallback</code> will change if <code class="notranslate">value</code> or <code class="notranslate">myCallback</code> changes</li>
<li>The <code class="notranslate">useEffect</code> will rerun if <code class="notranslate">myCallback</code>, <code class="notranslate">value</code> or <code class="notranslate">myOtherCallback</code> changes</li>
</ul>
<p dir="auto">We can instantly notice there is a lot of redundancy right there:</p>
<p dir="auto">If <code class="notranslate">value</code> changes, <code class="notranslate">myCallback</code> will change so I only need to specify <code class="notranslate">myCallback</code> as a dependency for <code class="notranslate">myOtherCallback</code> even though I also use <code class="notranslate">value</code> directly.<br>
The same reasoning can be applied to the dependencies of <code class="notranslate">useEffect</code>.</p>
<p dir="auto">As a human, I could omit <code class="notranslate">value</code> from the dependencies of <code class="notranslate">myOtherCallback</code> and be 100% absolutely guaranteed that the behavior would be strictly identical as per the hooks very own specifications.<br>
But if I do so, the <code class="notranslate">react-hooks/exhaustive-deps</code> eslint rule will complain that I'm missing some dependencies from the array.</p>
<h2 dir="auto">Proposal</h2>
<p dir="auto">If I understood properly, the rule does some kind of dependency traversal in order to attempt to recognize infinite loops.<br>
I think it would be nice for it to also be able to detect this kind of dependency redundancies.<br>
Even better, it should be able to take it into account when suggesting changes/reporting errors.</p>
<h2 dir="auto">Benefits</h2>
<p dir="auto">From reading the <a href="https://github.com/facebook/react/issues/14920" data-hovercard-type="issue" data-hovercard-url="/facebook/react/issues/14920/hovercard">issue</a> I mentionned at the top of this post, there is a fair amout of people who are not happy with the clutter the rule forces us to have in the codebase.<br>
Myself, I'm fairly hesitant and sometimes find myself thinking "I shouldn't need to specify this dep".<br>
Implementing the proposal mentionned above would be extremely beneficial regarding the readability of the code by totally preventing redundancy of dependencies.</p>
<h2 dir="auto">Drawbacks</h2>
<p dir="auto">I can see two potential issues with this proposal:</p>
<h4 dir="auto">Performance impact</h4>
<p dir="auto">Requiring the rule to recursively assess dependencies may have a performance impact on the linting.</p>
<p dir="auto">I'm not sure if this is the case since the rule does a similar check to try to avoid infinite loop. (Maybe I'm wrong and it's not the same thing).<br>
If this is too much of an impact, I think it could be opt-in behind a config flag.<br>
This would allow people to decide for themselves if the impact is worth it.</p>
<h4 dir="auto">Less obvious dependencies</h4>
<p dir="auto">Not explicitely specifying every used dependency may make it harder to assess the real update impact.<br>
From the above example, I know that my <code class="notranslate">useEffect</code> depends on <code class="notranslate">value</code>. If it weren't there, I would have to mentally climb up the dependencies myself.</p>
<p dir="auto">I think this is a fake drawback since I always need to climb up the dependencies tree to REALLY know what could cause my effect (or other) to be triggered.<br>
I would even argue that this proposal makes this mental parsing easier because I don't have any redundancy when reading the dependencies: every variable I see in a dependency array is specified exactly once. (Well, there may be exceptions when it comes to diverging dependencies but it's even more of an improvement in such a situation.)</p>
<h2 dir="auto">Conclusion</h2>
<p dir="auto">This proposal seems to be entirely positive from my point of view. The drawbacks are quite small and an opt-in design may make them even more manageable.</p>
<p dir="auto">eslint-plugin-react-hooks version: 4.0.4</p>
<p dir="auto">I have the following custom hook:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="export function useForceUpdate() {
  const [, setValue] = useState(0);
  return useCallback(() =&gt; setValue((v) =&gt; v + 1), []);
}"><pre class="notranslate"><span class="pl-k">export</span> <span class="pl-k">function</span> <span class="pl-en">useForceUpdate</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">const</span> <span class="pl-kos">[</span><span class="pl-kos">,</span> <span class="pl-s1">setValue</span><span class="pl-kos">]</span> <span class="pl-c1">=</span> <span class="pl-en">useState</span><span class="pl-kos">(</span><span class="pl-c1">0</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">return</span> <span class="pl-en">useCallback</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">setValue</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">v</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">v</span> <span class="pl-c1">+</span> <span class="pl-c1">1</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">But when I use it:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="function MyComponent(props) {
  const forceUpdate = useForceUpdate();

  // ... snip ...

  useEffect(() =&gt; {
    // do stuff
    forceUpdate();
  }, [foo, bar]);

  // ... continue
}"><pre class="notranslate"><span class="pl-k">function</span> <span class="pl-v">MyComponent</span><span class="pl-kos">(</span><span class="pl-s1">props</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">const</span> <span class="pl-s1">forceUpdate</span> <span class="pl-c1">=</span> <span class="pl-en">useForceUpdate</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-c">// ... snip ...</span>

  <span class="pl-en">useEffect</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-c">// do stuff</span>
    <span class="pl-s1">forceUpdate</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-s1">foo</span><span class="pl-kos">,</span> <span class="pl-s1">bar</span><span class="pl-kos">]</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-c">// ... continue</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">The linter complains (rightfully so):</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="warning  React Hook useEffect has a missing dependency: 'forceUpdate'. Either include it or remove the dependency array"><pre class="notranslate"><code class="notranslate">warning  React Hook useEffect has a missing dependency: 'forceUpdate'. Either include it or remove the dependency array
</code></pre></div>
<p dir="auto">It would useful to have a configuration option to tell the linter that <code class="notranslate">forceUpdate</code> is guaranteed to have a stable identity, so it can give it the same treatment as e.g. <code class="notranslate">useState</code>'s callback. Otherwise, I have to disable the rule every time I use the hook!</p>