-
Notifications
You must be signed in to change notification settings - Fork 558
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
RFC: Callback Ref Cleanup #205
base: main
Are you sure you want to change the base?
Conversation
text/0000-callback-ref-cleanup.md
Outdated
}} /> | ||
``` | ||
|
||
To keep backward compatibility, Callback Refs should still be called with `null` |
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.
This seems like it would be confusing because you can’t rely on the instance being there and it’s not fully symmetric.
What are the alternative designs? Considering we want all the existing code to continue to work if it doesn’t return a function. But maybe if it returns a function then we can change the behavior? There’s also a question of what happens when sometimes a ref returns a function and sometimes it doesn’t. Can you explore these issues and different options in more detail?
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 is possible to conditionally return a cleanup function from useEffect
. It should also be possible for the Callback Ref.
The reason why we should not call cleanup when the ref is null:
Because a ref is usually null when the parent component is unmounting. It is not clear when the cleanup should execute in this case. That is why I thought we should explicitly disallow it. But there is no harm in allowing it too. If we want to keep things symmetrical, we can call the cleanup right after calling the callback with null. In this case, user can decide if they want a cleanup of null ref, like in this example:
Both examples here: https://codesandbox.io/s/react-playground-forked-ng6x6?file=/index.js
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.
@gaearon I proposed another solution in the comment below.
There is another alternative. The problem with current callback refs is, So another ref type may be needed, a ref type which does not associate This ref type may have the signature (Typescript): interface RegisterRef<T> {
register: (ref: T) => void;
unregister?: (ref: T) => void;
} And this can be used like: const onClick = () => console.log('clicked!');
const logClicks = {
register: (node) => node.addEventListener('click', onClick);
unregister: (node) => node.removeEventListener('click', onClick);
};
function MyComponent(props) {
return <button ref={logClicks} />;
} Just like Although this solution is quite different than what is discussed in this RFC, I believe it solves the same problems. |
We're going to have a look at how often ref callbacks return functions today, and when that happens. This could give some sense of how disruptive this proposal could potentially be. facebook/react#22313 We don't think passing making the node nullable makes sense for the case where you return a function. Our current preferred version is where if you return a function, you opt into the new behavior, so you're not gonna get called with |
Even if callback refs don't currently return functions, there is another way that this proposal could potentially be disruptive: code that makes assumptions about how callback refs work. Of course, code that makes assumptions about how the React API works always risks breakage, but it can be useful in some cases. For example, in my own code I have made a hook that combines multiple callback refs from different sources into a single callback ref to be passed to a single element. This can be useful if you have multiple hooks for different purposes that both return callback refs, but you want to use both of them on the same element. (I suppose this could be a separate proposal: somehow supporting passing multiple refs, callback or object, to a single element.) Additionally, because React has to re-call the callback ref if the function value changes, in most cases it is better to use |
How does this work with forwardRef((props, passedRef) => {
const [isEditing, setIsEditing] = useState(false)
useImperativeHandle(passedRef, () => {
if (isEditing) {
return { selectText: () => {}, etc }
} else {
return null
}
}, [isEditing])
}) Also - was there anything broken with this pattern before? forwardRef((props, passedRef) => {
const ourRef = useRef()
useImperativeHandle(passedRef, () => ourRef.current, [])
return <div ref={ourRef} />
}) This always felt like the easiest way to clone refs. Seems like it would no longer work now though |
@jacobp100 As far as I understand, this change does not affect the behavior of your first code. Your second code seems wrong though. Changes to forwardRef((props, passedRef) => {
const [ourRef, setOurRef] = useState(null)
useImperativeHandle(passedRef, () => ourRef, [ourRef])
return <MyCustomComponent ref={setOurRef} />
}) |
Resources - RFC: reactjs/rfcs#205 - Warning implemented in #22313 - Warning enabled in #23145 - Feature added in #25686 We have warned to prevent the old behavior since 18.0.0. The new feature has been on in canary for a while but still triggering the warning. This PR cleans up the warning for 19
Resources - RFC: reactjs/rfcs#205 - Warning implemented in #22313 - Warning enabled in #23145 - Feature added in #25686 We have warned to prevent the old behavior since 18.0.0. The new feature has been on in canary for a while but still triggering the warning. This PR cleans up the warning for 19 DiffTrain build for commit db913d8.
Resources - RFC: reactjs/rfcs#205 - Warning implemented in #22313 - Warning enabled in #23145 - Feature added in #25686 We have warned to prevent the old behavior since 18.0.0. The new feature has been on in canary for a while but still triggering the warning. This PR cleans up the warning for 19 DiffTrain build for [db913d8](db913d8)
This RFC proposes adding a cleanup functionality to Callback Refs. See the issue this was discussed for more context.
View the formatted RFC