From 7377929e150268f7efe2d22865edefa31d848ad2 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Mon, 9 Sep 2024 20:32:19 -0700 Subject: [PATCH] RN: Reintroduce `forwardRef` to `ScrollView` (#46400) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46400 Since React 19 is not yet stable, we cannot publish a release of React Native depending on it, yet. As such, we have to revert our dependency on React 19 and bring back patterns such as `forwardRef`. This is a spiritual revert of https://github.com/facebook/react-native/pull/45197. Changelog: [Internal] Reviewed By: cipolleschi Differential Revision: D62384482 fbshipit-source-id: 880ad1166e0b449ad5be6f914907661adb5f458d --- .../Components/ScrollView/ScrollView.js | 21 +++++++++++-------- .../components/HScrollViewNativeComponents.js | 17 ++++++++------- .../components/VScrollViewNativeComponents.js | 17 ++++++++------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js index fc330dfd5c5..39260c22dff 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js @@ -1931,19 +1931,22 @@ function createRefForwarder( return state; } +// TODO: After upgrading to React 19, remove `forwardRef` from this component. // NOTE: This wrapper component is necessary because `ScrollView` is a class // component and we need to map `ref` to a differently named prop. This can be // removed when `ScrollView` is a functional component. -function Wrapper({ - ref, - ...props -}: { - ...Props, - ref: React.RefSetter, -}): React.Node { - return ; -} +const Wrapper = React.forwardRef(function Wrapper( + props: Props, + ref: ?React.RefSetter, +): React.Node { + return ref == null ? ( + + ) : ( + + ); +}); Wrapper.displayName = 'ScrollView'; +// $FlowExpectedError[prop-missing] Wrapper.Context = ScrollViewContext; module.exports = ((Wrapper: $FlowFixMe): React.AbstractComponent< diff --git a/packages/react-native/src/private/components/HScrollViewNativeComponents.js b/packages/react-native/src/private/components/HScrollViewNativeComponents.js index e335f53b780..9772dccb39d 100644 --- a/packages/react-native/src/private/components/HScrollViewNativeComponents.js +++ b/packages/react-native/src/private/components/HScrollViewNativeComponents.js @@ -21,33 +21,34 @@ import Platform from '../../../Libraries/Utilities/Platform'; import AndroidHorizontalScrollContentViewNativeComponent from '../specs/components/AndroidHorizontalScrollContentViewNativeComponent'; import useSyncOnScroll from './useSyncOnScroll'; import * as React from 'react'; +import {forwardRef} from 'react'; const HScrollViewNativeComponentForPlatform = Platform.OS === 'android' ? AndroidHorizontalScrollViewNativeComponent : ScrollViewNativeComponent; +// TODO: After upgrading to React 19, remove `forwardRef` from this component. export const HScrollViewNativeComponent: React.AbstractComponent< ScrollViewNativeProps, TScrollViewNativeImperativeHandle, // $FlowExpectedError[incompatible-type] - Flow cannot model imperative handles, yet. -> = function HScrollViewNativeComponent(props: { - ...ScrollViewNativeProps, - ref?: React.RefSetter, - ... -}): React.Node { - const [ref, enableSyncOnScroll] = useSyncOnScroll(props.ref); +> = forwardRef(function HScrollViewNativeComponent( + props: ScrollViewNativeProps, + ref: ?React.RefSetter, +): React.Node { + const [componentRef, enableSyncOnScroll] = useSyncOnScroll(ref); // NOTE: When `useSyncOnScroll` triggers an update, `props` will not have // changed. Notably, `props.children` will be the same, allowing React to // bail out during reconciliation. return ( ); -}; +}); export const HScrollContentViewNativeComponent: HostComponent = Platform.OS === 'android' diff --git a/packages/react-native/src/private/components/VScrollViewNativeComponents.js b/packages/react-native/src/private/components/VScrollViewNativeComponents.js index 57d9ba5bda0..89d17271f5b 100644 --- a/packages/react-native/src/private/components/VScrollViewNativeComponents.js +++ b/packages/react-native/src/private/components/VScrollViewNativeComponents.js @@ -20,28 +20,29 @@ import View from '../../../Libraries/Components/View/View'; import Platform from '../../../Libraries/Utilities/Platform'; import useSyncOnScroll from './useSyncOnScroll'; import * as React from 'react'; +import {forwardRef} from 'react'; +// TODO: After upgrading to React 19, remove `forwardRef` from this component. export const VScrollViewNativeComponent: React.AbstractComponent< ScrollViewNativeProps, TScrollViewNativeImperativeHandle, // $FlowExpectedError[incompatible-type] - Flow cannot model imperative handles, yet. -> = function VScrollViewNativeComponent(props: { - ...ScrollViewNativeProps, - ref?: React.RefSetter, - ... -}): React.Node { - const [ref, enableSyncOnScroll] = useSyncOnScroll(props.ref); +> = forwardRef(function VScrollViewNativeComponent( + props: ScrollViewNativeProps, + ref: ?React.RefSetter, +): React.Node { + const [componentRef, enableSyncOnScroll] = useSyncOnScroll(ref); // NOTE: When `useSyncOnScroll` triggers an update, `props` will not have // changed. Notably, `props.children` will be the same, allowing React to // bail out during reconciliation. return ( ); -}; +}); export const VScrollContentViewNativeComponent: HostComponent = Platform.OS === 'android' ? View : ScrollContentViewNativeComponent;