fix: Support React 19 ref cleanup functions in ScrollView#52361
fix: Support React 19 ref cleanup functions in ScrollView#52361ScypyonX wants to merge 2 commits into
Conversation
|
Hi @WojciechMichalowskii! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
Thanks for the contribution! I think it would be more forward-looking and easier to read if you instead change the returned |
|
@yungsters Thanks for answer ! The Problem: useEffect: useEffect(() => { return cleanup; }, []) - React automatically calls the returned cleanup function What happens with the suggested change: Component mounts → ref callback called with instance → cleanup function returned (but ignored by React) Tested code: type RefForwarder<TNativeInstance, TPublicInstance> = {
getForwardingRef: (
?React.RefSetter<TPublicInstance>,
) => (TNativeInstance | null) => () => void,
nativeInstance: TNativeInstance | null,
publicInstance: TPublicInstance | null,
};
/**
* Helper function that should be replaced with `useCallback` and `useMergeRefs`
* once `ScrollView` is reimplemented as a functional component.
*/
function createRefForwarder<TNativeInstance, TPublicInstance>(
mutator: TNativeInstance => TPublicInstance,
): RefForwarder<TNativeInstance, TPublicInstance> {
const state: RefForwarder<TNativeInstance, TPublicInstance> = {
getForwardingRef: memoize(forwardedRef => {
let cleanup: ?() => void = null;
return (nativeInstance: TNativeInstance | null): (() => void) => {
if (cleanup) {
cleanup();
cleanup = null;
}
const publicInstance =
nativeInstance == null ? null : mutator(nativeInstance);
state.nativeInstance = nativeInstance;
state.publicInstance = publicInstance;
if (forwardedRef != null) {
if (typeof forwardedRef === 'function') {
const result = forwardedRef(publicInstance);
if (typeof result === 'function') {
// $FlowFixMe[incompatible-use]
cleanup = result;
}
} else {
forwardedRef.current = publicInstance;
}
}
return () => {
if (cleanup) {
cleanup();
cleanup = null;
}
};
};
}),
nativeInstance: null,
publicInstance: null,
};
return state;
}
|
|
React 19 brought support for ref cleanup functions: https://react.dev/blog/2024/12/05/react-19#cleanup-functions-for-refs |
|
You are right - thanks for update my knowledge :) Fix pushed. |
|
@yungsters any updates? |
1 similar comment
|
@yungsters any updates? |
|
This PR is stale because it has been open for 180 days with no activity. It will be closed in 7 days unless you comment on it or remove the "Stale" label. |
|
This PR was closed because it has been stalled for 7 days with no activity. |

Summary:
ScrollView's
createRefForwarderfunction did not support React 19's new ref system where ref callbacks can return cleanup functions. This caused memory leaks on the C++ side as ShadowNodes couldn't be properly unmounted when ScrollView components were destroyed.The fix modifies
createRefForwarderto detect when ref callbacks return cleanup functions, store them, and call them appropriately when refs change or components unmount, while maintaining backward compatibility with legacy ref behavior.Resolves #51878
Changelog:
[GENERAL] [FIXED] - ScrollView now properly supports React 19 ref cleanup functions, preventing memory leaks
Test Plan:
nullon unmount (legacy behavior)Test code used:
Result: Cleanup functions are now properly called, resolving the memory leak issue reported in #51878.