-
Notifications
You must be signed in to change notification settings - Fork 47.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BUG] ref function gets called twice on update (but not on first mount), first call with null value. #9328
Comments
@gaearon So instead of fixing it the solution is to document the bad behavior? |
What do you mean by “fixing”? The behavior is intentional. If you pass an arrow function, referentially it is different every time. React has no way to know if it’s the same function in the code or not, so it has to “clean up” the old ref and “set up” the new ref just in case they turn out to be truly different functions. And cleaning up is necessary to avoid stale references (and memory leaks). In most code this shouldn’t matter. Again, you can avoid it if you make the ref callback into a class property or something that doesn’t change on every render. If you’re running into a case where this does matter please describe it. Generally we only recommend setting a field in the ref callback, and if you’re doing something more sophisticated, it might be better to move this logic to a lifecycle hook. |
I don't know how it is implemented, and as an end user I probably don't need to know. I'm just saying that this is a bug. The function doesn't need to be called with
I'm passing props directly to props, I wasn't desiring to have to write an extra class method each time I do that: class SomeComponent extend React.Component {
// ...
render() {
return <div>{this.items.map(i => <h1 ref={this.props.someFunction}></h1>)}</div>
}
// ...
} I don't want to have to write an extra class method just to pass along a prop. Also, the outer component that is using my component doesn't necessarily know how my component will use the function, and that outer component author shouldn't have to worry about the passed function being possibly and awkwardly called twice with |
It is documented, but it is still a bug. |
Why do you desire such behavior (having ref functions called twice each update, once with |
I believe I have explained why it is intentional in the above comment:
Just repeating it’s a bug is not a very productive way to frame this discussion. It is not a bug even if the behavior is a little confusing. As I mentioned it happens because you are technically passing a new function every time and so React has to clean up to avoid memory leaks and stale refs in case it really was a different function in the code. Unfortunately JavaScript does not give us a way to tell this with certainty so we have to play it safe. If you want to call some function every time the ref update you can do this from |
If I understand correctly, you're describing an implementation detail which is supposedly the reason why ref functions are called twice. However, I'm saying that from an outer-API end-user perspective, I don't care about the implementation reason, the behavior is a bug from an outer-API design perspective. If I read correctly, you're saying that the reason why it passes This implementation detail that you are describing doesn't necessarily stop leaks. For example, take the following ref function: <div ref={el => {if (!el) return; this.el = el}}></div> Notice that passing I believe that you're saying that passing In general, you can't really guarantee that some API makes the end-user's code 100% memory-leak free. At least, not with the current implementation and API surface. As with the vast majority of JavaScript libraries and framework in existence, preventing memory leaks is the job of the app developer when writing the app, and the library author on the library internals of the library used by the app developer, but the lines don't blend. Library authors can't really prevent app developers from writing memory leaks if the implementation only prevents memory leaks in a fraction of all use cases. If library implementation should prevent outer-API-side app-developer leaks, then it needs to prevent 100% of the leaks in all possible use cases of the outer-API, but it cannot guarantee 100% memory-leak prevention is areas outside of the API's control. Currently, React ref function API doesn't control what goes inside a ref function, and therefore it isn't necessary to call ref functions with If you want to prevent memory leaks with refs in a more certain way, then go back to the deprecated string method for refs, make it read-only (f.e. freeze the If you provide an API that accepts a user-defined function, you can't possibly assume you know how to prevent the end-user's memory leaks, and you can't guarantee that a user won't create a leak in it by doing something different than you expect. Honestly, a novice programmer is going to make leaks, and it isn't going to be because of React. You simply don't need to pass |
On top of that, I'm willing to bet that Garbage Collection is really good these days, so if a ref function is only |
I've never seen an API like this, that explicitly passes a It's like if I give you a function called doMath(5, function(answer) {
if (!answer) return // first call is null
console.log(answer) // second call gives answer
}) That would be strange, just like this ref function behavior is. |
@gaearon I made the new issue because you closed this one, which hides the issue. |
I saw (and closed it). Sorry, but this is not how open source projects on GitHub generally work. I am happy to continue the discussion here, and allocate some time to answering to your comments. Please consider that your issue is in direct competition with 565 open issues. I generally try to answer when I find the time, and you can just ping me on this thread again once in a while if you feel like the communication is not enough. |
Yes, but at least it is possible to clean up. It “just works” in most simple cases (like you mentioned), and it is possible in more complex cases. I will split this answer into
Why we call
|
Regarding your question:
Consider this part of the ref callback contract. There are many different function contracts: a function that returns a Promise, a Node-style errback function, a reducer, etc. React ref callback is also such a contract, and we use If it doesn’t make sense to expose this contract to the outer component, you can always wrap the function provided by the user, or the other way around, to modify the contract at the seam where it’s happening. But you could also make it clear that you’re exposing that contract by naming a prop with a |
Future way: RFC #17 — New createRef() API for ref objects |
Object refs landed in 16.3 and solve this inconvenience for most cases: |
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:
In
componentDidMount
, I can verify thatthis.frames
containsthis.props.totalFrames
.However, on
componentDidUpdate
,this.frames
has a length of doublethis.props.totalFrame
, where the first half of the items are allnull
. This means that subsequentrender
calls after the first are first passingnull
into theref={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: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 settingthis.frames
to a new array insiderender
, thencomponentDidUpdate
shows that it has double the length, with the first half of the values allnull
.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.The text was updated successfully, but these errors were encountered: