<p dir="auto"><strong>Do you want to request a <em>feature</em> or report a <em>bug</em>?</strong></p>
<p dir="auto">Bug, perhaps just in documentation.</p>
<p dir="auto"><strong>What is the current behavior?</strong></p>
<p dir="auto">The documentation for <a href="https://facebook.github.io/react/docs/refs-and-the-dom.html" rel="nofollow">Refs and the DOM</a> says:</p>
<blockquote>
<p dir="auto">The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.</p>
</blockquote>
<p dir="auto">But if I create a stateful component like:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class ScrollHelper extends React.PureComponent {
  
  componentDidMount() {
    console.log(&quot;ScrollHelper componentDidMount&quot;)
  }

  componentWillUnmount() {
    console.log(&quot;ScrollHelper componentWillUnmount&quot;)
  }
  
  render() {
    console.log(&quot;ScrollHelper render&quot;)
    return &lt;div/&gt;
}"><pre class="notranslate"><code class="notranslate">class ScrollHelper extends React.PureComponent {
  
  componentDidMount() {
    console.log("ScrollHelper componentDidMount")
  }

  componentWillUnmount() {
    console.log("ScrollHelper componentWillUnmount")
  }
  
  render() {
    console.log("ScrollHelper render")
    return &lt;div/&gt;
}
</code></pre></div>
<p dir="auto">and use it via <code class="notranslate">&lt;ScrollHelper ref={(obj) =&gt; console.log("ScrollHelper ref", obj)}/&gt;</code> within an app, I do not see the behavior documented, but rather the following pattern of logs:</p>
<blockquote>
<p dir="auto">// [first render]<br>
ScrollHelper render<br>
ScrollHelper componentDidMount<br>
ScrollHelper ref &lt;{props: etc…}&gt;<br>
// [some data changes elsewhere in the app]<br>
ScrollHelper ref &lt;null&gt;<br>
ScrollHelper render<br>
ScrollHelper ref &lt;{props: etc…}&gt;<br>
// [some data changes elsewhere in the app, again…]<br>
ScrollHelper ref &lt;null&gt;<br>
ScrollHelper render<br>
ScrollHelper ref &lt;{props: etc…}&gt;<br>
// [some data changes causing the ScrollHelper to actually unmount]<br>
ScrollHelper ref &lt;null&gt;<br>
ScrollHelper componentWillUnmount</p>
</blockquote>
<p dir="auto"><strong>What is the expected behavior?</strong></p>
<p dir="auto">According to the docs, I would expect my <code class="notranslate">refs</code> callback to be called exactly <strong>two</strong> times during the events above — once (with the component) after the "ScrollHelper componentDidMount" and once (with <code class="notranslate">null</code>) before the "ScrollHelper componentWillUnmount". Instead it gets "reset" basically every time the page is re-rendered — even though it's pretty clearly ending up with the same component instance each time! (I verified this is true via some additional code as well, but should be evident from the lack of additional "componentDidMount" logs anyway.)</p>
<p dir="auto"><strong>Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?</strong></p>
<p dir="auto">react@15.4.1, no idea what the behavior was earlier.</p>
<p dir="auto"><strong>Do you want to request a <em>feature</em> or report a <em>bug</em>?</strong></p>
<p dir="auto">bug</p>
<p dir="auto"><strong>What is the current behavior?</strong></p>
<p dir="auto"><code class="notranslate">ref</code> functions get called twice on update (but not on first mount).</p>
<p dir="auto"><strong>If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via <a href="https://jsfiddle.net" rel="nofollow">https://jsfiddle.net</a> or similar (template: <a href="https://jsfiddle.net/84v837e9/" rel="nofollow">https://jsfiddle.net/84v837e9/</a>).</strong></p>
<p dir="auto">I have some code like this:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="render() {
  const frames = []
  frames.length = this.props.totalFrames

  for (let i=0, l=frames.length; i&lt;l; i+=1)
      frames[i] = i

  this.frames = []

  return (
      &lt;div&gt;
          {frames.map(frame =&gt;
              &lt;div className=&quot;frame&quot; key={frame} ref={el =&gt; {this.frames.push(el)}}&gt;
              &lt;/div&gt;
          )}
      &lt;/div&gt;
  )
}"><pre class="notranslate"><span class="pl-en">render</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">frames</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-kos">]</span>
  <span class="pl-s1">frames</span><span class="pl-kos">.</span><span class="pl-c1">length</span> <span class="pl-c1">=</span> <span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">props</span><span class="pl-kos">.</span><span class="pl-c1">totalFrames</span>

  <span class="pl-k">for</span> <span class="pl-kos">(</span><span class="pl-k">let</span> <span class="pl-s1">i</span><span class="pl-c1">=</span><span class="pl-c1">0</span><span class="pl-kos">,</span> <span class="pl-s1">l</span><span class="pl-c1">=</span><span class="pl-s1">frames</span><span class="pl-kos">.</span><span class="pl-c1">length</span><span class="pl-kos">;</span> <span class="pl-s1">i</span><span class="pl-c1">&lt;</span><span class="pl-s1">l</span><span class="pl-kos">;</span> <span class="pl-s1">i</span><span class="pl-c1">+=</span><span class="pl-c1">1</span><span class="pl-kos">)</span>
      <span class="pl-s1">frames</span><span class="pl-kos">[</span><span class="pl-s1">i</span><span class="pl-kos">]</span> <span class="pl-c1">=</span> <span class="pl-s1">i</span>

  <span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">frames</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-kos">]</span>

  <span class="pl-k">return</span> <span class="pl-kos">(</span>
      <span class="pl-c1">&lt;</span><span class="pl-ent">div</span><span class="pl-c1">&gt;</span>
          <span class="pl-kos">{</span><span class="pl-s1">frames</span><span class="pl-kos">.</span><span class="pl-en">map</span><span class="pl-kos">(</span><span class="pl-s1">frame</span> <span class="pl-c1">=&gt;</span>
              <span class="pl-c1">&lt;</span><span class="pl-ent">div</span> <span class="pl-c1">className</span><span class="pl-c1">=</span><span class="pl-s">"frame"</span> <span class="pl-c1">key</span><span class="pl-c1">=</span><span class="pl-kos">{</span><span class="pl-s1">frame</span><span class="pl-kos">}</span> <span class="pl-c1">ref</span><span class="pl-c1">=</span><span class="pl-kos">{</span><span class="pl-s1">el</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span><span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">frames</span><span class="pl-kos">.</span><span class="pl-en">push</span><span class="pl-kos">(</span><span class="pl-s1">el</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-c1">&lt;</span><span class="pl-c1">/</span><span class="pl-ent">div</span><span class="pl-c1">&gt;</span>
          <span class="pl-kos">)</span><span class="pl-kos">}</span>
      <span class="pl-c1">&lt;</span><span class="pl-c1">/</span><span class="pl-ent">div</span><span class="pl-c1">&gt;</span>
  <span class="pl-kos">)</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">In <code class="notranslate">componentDidMount</code>, I can verify that <code class="notranslate">this.frames</code> contains <code class="notranslate">this.props.totalFrames</code>.</p>
<p dir="auto">However, on <code class="notranslate">componentDidUpdate</code>, <code class="notranslate">this.frames</code> has a length of double <code class="notranslate">this.props.totalFrame</code>, where the first half of the items are all <code class="notranslate">null</code>. This means that subsequent <code class="notranslate">render</code> calls after the first are first passing <code class="notranslate">null</code> into the <code class="notranslate">ref={el =&gt; {this.frames.push(el)}</code> function for each element followed by the correct non-null values.</p>
<p dir="auto">For example, if <code class="notranslate">this.props.totalFrames</code> is 3, then we observe this:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="componentDidMount() {
  console.log(this.frames)
  // output:
  // [div, div, div]
}

componentDidUpdate() {
  console.log(this.frames)
  // output:
  // [null, null, null, div, div, div]
}"><pre class="notranslate"><span class="pl-en">componentDidMount</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos"></span> <span class="pl-kos">{</span>
  <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">frames</span><span class="pl-kos">)</span>
  <span class="pl-c">// output:</span>
  <span class="pl-c">// [div, div, div]</span>
<span class="pl-kos">}</span>

<span class="pl-en">componentDidUpdate</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos"></span> <span class="pl-kos">{</span>
  <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">frames</span><span class="pl-kos">)</span>
  <span class="pl-c">// output:</span>
  <span class="pl-c">// [null, null, null, div, div, div]</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">where <code class="notranslate">this.frames</code> is always twice as expected.</p>
<p dir="auto"><strong>What is the expected behavior?</strong></p>
<p dir="auto">The array should not contain null values, only the divs.</p>
<p dir="auto">NOTE, <code class="notranslate">render()</code> is <em>not</em> being called more than once, and I am setting <code class="notranslate">this.frames</code> to a new array inside <code class="notranslate">render</code>, then <code class="notranslate">componentDidUpdate</code> shows that it has double the length, with the first half of the values all <code class="notranslate">null</code>.</p>
<p dir="auto">It seems like React is calling the ref functions one time too many for each frame element, the first call with a <code class="notranslate">null</code> value, the second call with the expected value.</p>
<p dir="auto"><strong>Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?</strong></p>
<p dir="auto">React 15.4.2</p>
<p dir="auto"><strong>Workaround</strong></p>
<p dir="auto">As a workaround, I can simply and consistently <code class="notranslate">filter()</code> the null values out before interacting with the array.</p>