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
This commit is contained in:
Tim Yung
2024-09-09 20:32:19 -07:00
committed by Facebook GitHub Bot
parent 1f65bf9545
commit 7377929e15
3 changed files with 30 additions and 25 deletions
@@ -1931,19 +1931,22 @@ function createRefForwarder<TNativeInstance, TPublicInstance>(
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<PublicScrollViewInstance>,
}): React.Node {
return <ScrollView {...props} scrollViewRef={ref} />;
}
const Wrapper = React.forwardRef(function Wrapper(
props: Props,
ref: ?React.RefSetter<PublicScrollViewInstance>,
): React.Node {
return ref == null ? (
<ScrollView {...props} />
) : (
<ScrollView {...props} scrollViewRef={ref} />
);
});
Wrapper.displayName = 'ScrollView';
// $FlowExpectedError[prop-missing]
Wrapper.Context = ScrollViewContext;
module.exports = ((Wrapper: $FlowFixMe): React.AbstractComponent<
@@ -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<TScrollViewNativeImperativeHandle | null>,
...
}): React.Node {
const [ref, enableSyncOnScroll] = useSyncOnScroll(props.ref);
> = forwardRef(function HScrollViewNativeComponent(
props: ScrollViewNativeProps,
ref: ?React.RefSetter<TScrollViewNativeImperativeHandle | null>,
): 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 (
<HScrollViewNativeComponentForPlatform
{...props}
ref={ref}
ref={componentRef}
enableSyncOnScroll={enableSyncOnScroll}
/>
);
};
});
export const HScrollContentViewNativeComponent: HostComponent<ViewProps> =
Platform.OS === 'android'
@@ -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<TScrollViewNativeImperativeHandle | null>,
...
}): React.Node {
const [ref, enableSyncOnScroll] = useSyncOnScroll(props.ref);
> = forwardRef(function VScrollViewNativeComponent(
props: ScrollViewNativeProps,
ref: ?React.RefSetter<TScrollViewNativeImperativeHandle | null>,
): 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 (
<ScrollViewNativeComponent
{...props}
ref={ref}
ref={componentRef}
enableSyncOnScroll={enableSyncOnScroll}
/>
);
};
});
export const VScrollContentViewNativeComponent: HostComponent<ViewProps> =
Platform.OS === 'android' ? View : ScrollContentViewNativeComponent;