title: Potential bug in "official" useInterval example
body: <h1>useInterval</h1>
  <p><code>useInterval</code> from <a href="https://overreacted.io/making-setinterval-declarative-with-react-hooks/" rel="nofollow noreferrer">this blog post by Dan Abramov (2019)</a>:</p>
  <pre class="lang-js prettyprint-override"><code>function useInterval(callback, delay) {
    const savedCallback = useRef();
  
    // Remember the latest callback.
    useEffect(() =&gt; {
      savedCallback.current = callback;
    }, [callback]);
  
    // Set up the interval.
    useEffect(() =&gt; {
      function tick() {
        savedCallback.current();
      }
      if (delay !== null) {
        let id = setInterval(tick, delay);
        return () =&gt; clearInterval(id);
      }
    }, [delay]);
  }
  </code></pre>
  <h1>A Potential Bug</h1>
  <p>The interval callback may be invoked between the commit phase and the <code>useEffect</code> invocation, causing the old (and hence not up to date) callback to be called. In other words, this may be the execution order:</p>
  <ol>
  <li><strong>Render phase</strong> - a new value for <code>callback</code>.</li>
  <li><strong>Commit phase</strong> - state committed to DOM.</li>
  <li><strong>useLayoutEffect</strong></li>
  <li><strong>Interval callback</strong> - using <code>savedCallback.current()</code>, which is different than <code>callback</code>.</li>
  <li><strong>useEffect</strong> - <code>savedCallback.current = callback;</code></li>
  </ol>
  <h1>React's Life Cycle</h1>
  <p>To further illustrate this, here's <a href="https://github.com/donavon/hook-flow" rel="nofollow noreferrer">a diagram showing React's Life Cycle with hooks</a>:</p>
  <p><img src="https://raw.githubusercontent.com/donavon/hook-flow/master/hook-flow.png" alt="React lifecycle for functional components" /></p>
  <p>Dashed lines mean async flow (event loop released) and you can have an interval callback invocation at these points.</p>
  <p><strong>Note however,</strong> that the dashed line between <code>Render</code> and <code>React updates DOM</code> (commit phase) is most likely <a href="https://github.com/donavon/hook-flow/issues/10" rel="nofollow noreferrer">a mistake</a>. As <a href="https://codesandbox.io/s/react-life-cycle-and-intervals-9rshs" rel="nofollow noreferrer">this codesandbox</a> demonstrates, you can only have an interval callback invoked after <code>useLayoutEffect</code> or <code>useEffect</code> (but not after the render phase).</p>
  <p>So you can set the callback in 3 places:</p>
  <ul>
  <li>Render - Incorrect because state changes have not yet been committed to the DOM.</li>
  <li><code>useLayoutEffect</code> - correct because state changes have been committed to the DOM.</li>
  <li><code>useEffect</code> - incorrect because the old interval callback may fire before that (after layout effects) .</li>
  </ul>
  <h1>Demo</h1>
  <p>This bug is demonstrated in this <a href="https://codesandbox.io/s/useintervalbug-59wd2" rel="nofollow noreferrer">codesandebox</a>. To reproduce:</p>
  <ul>
  <li>Move the mouse over the grey div - this will lead to a new render with a new <code>callback</code> reference.</li>
  <li>Typically you'll see the error thrown in less than 2000 mouse moves.</li>
  <li>The interval is set to 50ms, so you need a bit of luck for it to fire right between the render and effect phases.</li>
  </ul>
  <p><a href="https://i.stack.imgur.com/msPJT.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/msPJT.png" alt="A screenshot of a code sandbox showing an exception thrown when the effect callback is not up to date" /></a></p>
  <h1>Use Case</h1>
  <p>The demo shows that the current callback value may differ from that in <code>useEffect</code> alright, but the real question is <strong>which one of them is the &quot;correct&quot; one</strong>?</p>
  <p>Consider this code:</p>
  <pre class="lang-js prettyprint-override"><code>const [size, setSize] = React.useState();
  
  const onInterval = () =&gt; {
    console.log(size)
  }
  
  useInterval(onInterval, 100);
  </code></pre>
  <p>If <code>onInterval</code> is invoked after the commit phase but before <code>useEffect</code>, it will print the wrong value.</p>