-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Ref merging: fix memory leak #27686
Ref merging: fix memory leak #27686
Conversation
Size Change: +52 B (0%) Total Size: 1.28 MB
ℹ️ View Unchanged
|
* @return {Function} A new ref callback. | ||
*/ | ||
export default function useMergeRefs( ...refs ) { | ||
return useCallback( mergeRefs( refs ), refs ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, thinking more, the addition of "refs" as a dependency is not a good thing. If one of the refs change, all the refs will be recalled again even the ones that didn't change. Not sure exactly how to solve this. We could solve this by "convention".
Meaning we make sure we only use useMergeRefs
for refs that don't change over time (so even the useCallback we use right now for these refs is not necessary. It doesn't sound great though, so I'm wondering if there's a smarter solution (only call the ref function again if it changes). I also wonder whether such thing exists in npm already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I'm not sure if it a good thing, because on the one hand there's the assumption that a ref callback shouldn't be called if there's no new node, and on the other hand, there's the dependency issue, which you've resolved before by setting the dependencies as refs.
I think we should always return the same callback throughout the lifecycle as refs are supposed to behave.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another way is to keep using mergeRefs
like today and just "memoize" the ref callbacks. I think it might be the best way. We could make it a hook, useMemoizeCallback or something like that :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean replace useCallback
with useMemo
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, I mean const ref = useCallback( memize( (node) => { // do something } ), [ deps ] )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's what we want, but now we are adjusting to a ref callback not being "purely" called when the node changes (or is added or removed). To me the expectation I have with a ref callback is that it's never called multiple times with the same node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me "purely" called doesn't mean much. useMemo or useCallback do the same thing as memize behind the scenes. I don't really see the difference. The difference is in one case, the memorization is inside React itself while in the other case it's outside React.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think there's benefit in wrapping the function in useCallback
, and add the refs as dependencies, which makes the makes all the inner useCallback
s work correctly. What's the point otherwise of wrapping ref callbacks in useCallback
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the point is that the a new function is generated for the ref when one of the deps change which calls the callback again with the node, allowing you to react properly to these deps changes.
02041d7
to
886c5a8
Compare
Description
Currently, in
useConstrainedTabbing
(and probably other hooks returning a ref), code is executed multiple times on the same node. InuseConstrainedTabbing
this results in event listeners being added multiple times.It's assumed that a ref callback will only be called when the node changes or the component unmounts, but that's not the case if callbacks are merged with
mergeRefs
and directly passed as the ref.mergeRefs
returns a new function on every render, while it should return a persistent callback.How has this been tested?
In
master
, log thenode
right beforenode.addEventListener
in theuseConstrainedTabbing
hook. Open the editor, then "Options" in the top right corner and then click "Preferences" to open a modal. Observe that the modal node is logged twice, which means the event listener is added twice.Now check out this branch and follow the steps again. The modal node will only be logged once.
Screenshots
Types of changes
Checklist: