Issue 1:
Do you want to request a feature or report a bug?

Bug, perhaps just in documentation.

What is the current behavior?

The documentation for Refs and the DOM says:

The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.

But if I create a stateful component like:

class ScrollHelper extends React.PureComponent {

componentDidMount() {
console.log("ScrollHelper componentDidMount")
}

componentWillUnmount() {
console.log("ScrollHelper componentWillUnmount")
}

render() {
console.log("ScrollHelper render")
return <div/>
}

and use it via <ScrollHelper ref={(obj) => console.log("ScrollHelper ref", obj)}/> within an app, I do not see the behavior documented, but rather the following pattern of logs:

// [first render]

ScrollHelper render

ScrollHelper componentDidMount

ScrollHelper ref <{props: etc…}>

// [some data changes elsewhere in the app]

ScrollHelper ref <null>

ScrollHelper render

ScrollHelper ref <{props: etc…}>

// [some data changes elsewhere in the app, again…]

ScrollHelper ref <null>

ScrollHelper render

ScrollHelper ref <{props: etc…}>

// [some data changes causing the ScrollHelper to actually unmount]

ScrollHelper ref <null>

ScrollHelper componentWillUnmount

What is the expected behavior?

According to the docs, I would expect my refs callback to be called exactly two times during the events above — once (with the component) after the "ScrollHelper componentDidMount" and once (with null) 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.)

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

react@15.4.1, no idea what the behavior was earlier.

Issue 2:
Do you want to request a feature or report a bug?

bug

What is the current behavior?

ref functions get called twice on update (but not on first mount).

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/84v837e9/).

I have some code like this:

render() {
const frames = []
frames.length = this.props.totalFrames

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

this.frames = []

return (
<div>
{frames.map(frame =>
<div className="frame" key={frame} ref={el => {this.frames.push(el)}}>
</div>
)}
</div>
)
}

In componentDidMount, I can verify that this.frames contains this.props.totalFrames.

However, on componentDidUpdate, this.frames has a length of double this.props.totalFrame, where the first half of the items are all null. This means that subsequent render calls after the first are first passing null into the ref={el => {this.frames.push(el)} function for each element followed by the correct non-null values.

For example, if this.props.totalFrames is 3, then we observe this:

componentDidMount() {
console.log(this.frames)
// output:
// [div, div, div]
}

componentDidUpdate() {
console.log(this.frames)
// output:
// [null, null, null, div, div, div]
}

where this.frames is always twice as expected.

What is the expected behavior?

The array should not contain null values, only the divs.

NOTE, render() is not being called more than once, and I am setting this.frames to a new array inside render, then componentDidUpdate shows that it has double the length, with the first half of the values all null.

It seems like React is calling the ref functions one time too many for each frame element, the first call with a null value, the second call with the expected value.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React 15.4.2

Workaround

As a workaround, I can simply and consistently filter() the null values out before interacting with the array.