From 520938ec2452fbac7e8a84f9efca4fe93e89d277 Mon Sep 17 00:00:00 2001 From: Bartlomiej Bloniarz Date: Thu, 8 Oct 2026 07:49:17 -0700 Subject: [PATCH] Generate `initialHidden` as an optional prop for VirtualView (#58864) Summary: `VirtualViewNativeComponent` declared `initialHidden?: boolean`, which codegen generated as a plain `bool` defaulting to `false`. On Android, props equal to their C++ default are not sent when a view is created, so an explicit `initialHidden={false}` never reached `ReactVirtualViewManager.setInitialHidden`. The view's `mode` stayed `null`, so it emitted a synchronous `Visible` mode change on its first update, emitted a normally suppressed `Prerender` change, and skipped the clipping update when it first left `Visible`. Declare the prop as `WithDefault` and opt the spec into `generateOptionalProperties`, so `initialHidden` is a `std::optional` that is unset by default and an explicit `false` is sent. A plain `?: boolean` is not enough, because the Flow parser assigns it a `false` default. The `codegenNativeComponent` Flow options type gains the codegen options already declared in its TypeScript types, and iOS reads the prop with `value_or(false)`. This changes generated APIs of VirtualView: `VirtualViewProps::initialHidden` becomes `std::optional`, and `VirtualViewManagerInterface.setInitialHidden` takes a nullable `Boolean`. The public JS API change is additive: `NativeComponentOptions` gains the optional `generateOptionalProperties` and `generateOptionalObjectProperties` fields, so the API snapshot reports `codegenNativeComponent` as changed without breaking existing callers. Changelog: [General][Fixed] - Fix `VirtualView` initial mode when `initialHidden` is not sent on creation Reviewed By: javache Differential Revision: D123029234 --- .../Libraries/Utilities/codegenNativeComponent.js | 2 ++ .../VirtualView/RCTVirtualViewComponentView.mm | 2 +- packages/react-native/ReactAndroid/api/ReactAndroid.api | 6 +++--- .../react/views/virtual/view/ReactVirtualViewManager.kt | 4 ++-- packages/react-native/ReactNativeApi.d.ts | 6 ++++-- .../components/virtualview/VirtualViewNativeComponent.js | 4 +++- scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api | 2 +- scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api | 2 +- scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api | 2 +- scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api | 2 +- scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api | 2 +- scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api | 2 +- scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api | 2 +- scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api | 2 +- 14 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/react-native/Libraries/Utilities/codegenNativeComponent.js b/packages/react-native/Libraries/Utilities/codegenNativeComponent.js index 38a664f90c9e..3df58c7e5a45 100644 --- a/packages/react-native/Libraries/Utilities/codegenNativeComponent.js +++ b/packages/react-native/Libraries/Utilities/codegenNativeComponent.js @@ -21,6 +21,8 @@ type NativeComponentOptions = Readonly<{ paperComponentName?: string, paperComponentNameDeprecated?: string, excludedPlatforms?: ReadonlyArray<'iOS' | 'android'>, + generateOptionalProperties?: boolean, + generateOptionalObjectProperties?: boolean, }>; // Note that this alias is not re-exported from the `react-native` root, diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm index 7ced2e932d92..384e1f658b5f 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm @@ -63,7 +63,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & const auto &newViewProps = static_cast(*props); if (!_mode.has_value()) { - _mode = newViewProps.initialHidden ? RCTVirtualViewModeHidden : RCTVirtualViewModeVisible; + _mode = newViewProps.initialHidden.value_or(false) ? RCTVirtualViewModeHidden : RCTVirtualViewModeVisible; } switch (newViewProps.renderState) { diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 2aa6ba5da679..f4c533958816 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -5152,7 +5152,7 @@ public class com/facebook/react/viewmanagers/VirtualViewManagerDelegate : com/fa } public abstract interface class com/facebook/react/viewmanagers/VirtualViewManagerInterface : com/facebook/react/uimanager/ViewManagerWithGeneratedInterface { - public abstract fun setInitialHidden (Landroid/view/View;Z)V + public abstract fun setInitialHidden (Landroid/view/View;Ljava/lang/Boolean;)V public abstract fun setRemoveClippedSubviews (Landroid/view/View;Z)V public abstract fun setRenderState (Landroid/view/View;I)V } @@ -6580,8 +6580,8 @@ public final class com/facebook/react/views/virtual/view/ReactVirtualViewManager public synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View; public fun getName ()Ljava/lang/String; public synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View; - public synthetic fun setInitialHidden (Landroid/view/View;Z)V - public fun setInitialHidden (Lcom/facebook/react/views/virtual/view/ReactVirtualView;Z)V + public synthetic fun setInitialHidden (Landroid/view/View;Ljava/lang/Boolean;)V + public fun setInitialHidden (Lcom/facebook/react/views/virtual/view/ReactVirtualView;Ljava/lang/Boolean;)V public synthetic fun setNativeId (Landroid/view/View;Ljava/lang/String;)V public fun setNativeId (Lcom/facebook/react/views/virtual/view/ReactVirtualView;Ljava/lang/String;)V public synthetic fun setRemoveClippedSubviews (Landroid/view/View;Z)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualViewManager.kt index a442d77c796f..e5359bc89c69 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualViewManager.kt @@ -37,9 +37,9 @@ public class ReactVirtualViewManager : ReactVirtualView(reactContext) @ReactProp(name = "initialHidden") - override fun setInitialHidden(view: ReactVirtualView, value: Boolean) { + override fun setInitialHidden(view: ReactVirtualView, value: Boolean?) { if (view.mode == null) { - view.mode = if (value) VirtualViewMode.Hidden else VirtualViewMode.Visible + view.mode = if (value == true) VirtualViewMode.Hidden else VirtualViewMode.Visible } } diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index a549b879b33d..52e4bbc932a8 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6d379de092b8bad36a420658bbc25a6d>> + * @generated SignedSource<> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -3072,6 +3072,8 @@ declare type NativeCommandsOptions = { } declare type NativeComponentOptions = { readonly excludedPlatforms?: ReadonlyArray<"android" | "iOS"> + readonly generateOptionalObjectProperties?: boolean + readonly generateOptionalProperties?: boolean readonly interfaceOnly?: boolean readonly paperComponentName?: string readonly paperComponentNameDeprecated?: string @@ -6055,7 +6057,7 @@ export { VirtualizedSectionListProps, // 8373f27c WrapperComponentProvider, // 9b4247f6 codegenNativeCommands, // 322f3f4e - codegenNativeComponent, // e8334853 + codegenNativeComponent, // 3da62daa findNodeHandle, // 93f80214 processColor, // 6e877698 registerCallableModule, // 839c8cfe diff --git a/packages/react-native/src/private/components/virtualview/VirtualViewNativeComponent.js b/packages/react-native/src/private/components/virtualview/VirtualViewNativeComponent.js index 4ba5b68398ed..80fbd5c8efb1 100644 --- a/packages/react-native/src/private/components/virtualview/VirtualViewNativeComponent.js +++ b/packages/react-native/src/private/components/virtualview/VirtualViewNativeComponent.js @@ -13,6 +13,7 @@ import type { DirectEventHandler, Double, Int32, + WithDefault, } from '../../../../Libraries/Types/CodegenTypes'; import type {HostComponent} from '../../types/HostComponent'; @@ -65,7 +66,7 @@ type VirtualViewNativeProps = Readonly<{ /** * Whether the initial mode should be `Hidden`. */ - initialHidden?: boolean, + initialHidden?: WithDefault, /** * This was needed to get VirtualViewManagerDelegate to set this property. @@ -93,4 +94,5 @@ type VirtualViewNativeProps = Readonly<{ export default codegenNativeComponent('VirtualView', { interfaceOnly: true, + generateOptionalProperties: true, }) as HostComponent; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 777b8427b4ba..9256ca1ecf73 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -2575,9 +2575,9 @@ struct facebook::react::VirtualViewEventEmitter::OnModeChangeThresholdRect { class facebook::react::VirtualViewProps : public facebook::react::HostPlatformViewProps { public VirtualViewProps() = default; public VirtualViewProps(const facebook::react::PropsParserContext& context, const facebook::react::VirtualViewProps& sourceProps, const facebook::react::RawProps& rawProps); - public bool initialHidden; public bool removeClippedSubviews; public int renderState; + public std::optional initialHidden; public virtual facebook::react::ComponentName getDiffPropsImplementationTarget() const override; public virtual folly::dynamic getDiffProps(const facebook::react::Props* prevProps) const override; } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 07a514f87218..79a7ca0a609d 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -4415,9 +4415,9 @@ struct facebook::react::VirtualViewEventEmitter::OnModeChangeThresholdRect { class facebook::react::VirtualViewProps : public facebook::react::HostPlatformViewProps { public VirtualViewProps() = default; public VirtualViewProps(const facebook::react::PropsParserContext& context, const facebook::react::VirtualViewProps& sourceProps, const facebook::react::RawProps& rawProps); - public bool initialHidden; public bool removeClippedSubviews; public int renderState; + public std::optional initialHidden; public virtual facebook::react::ComponentName getDiffPropsImplementationTarget() const override; public virtual folly::dynamic getDiffProps(const facebook::react::Props* prevProps) const override; } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index e61666350cc0..1caed6d9fb24 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -2568,9 +2568,9 @@ struct facebook::react::VirtualViewEventEmitter::OnModeChangeThresholdRect { class facebook::react::VirtualViewProps : public facebook::react::HostPlatformViewProps { public VirtualViewProps() = default; public VirtualViewProps(const facebook::react::PropsParserContext& context, const facebook::react::VirtualViewProps& sourceProps, const facebook::react::RawProps& rawProps); - public bool initialHidden; public bool removeClippedSubviews; public int renderState; + public std::optional initialHidden; public virtual facebook::react::ComponentName getDiffPropsImplementationTarget() const override; public virtual folly::dynamic getDiffProps(const facebook::react::Props* prevProps) const override; } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index e61666350cc0..1caed6d9fb24 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -2568,9 +2568,9 @@ struct facebook::react::VirtualViewEventEmitter::OnModeChangeThresholdRect { class facebook::react::VirtualViewProps : public facebook::react::HostPlatformViewProps { public VirtualViewProps() = default; public VirtualViewProps(const facebook::react::PropsParserContext& context, const facebook::react::VirtualViewProps& sourceProps, const facebook::react::RawProps& rawProps); - public bool initialHidden; public bool removeClippedSubviews; public int renderState; + public std::optional initialHidden; public virtual facebook::react::ComponentName getDiffPropsImplementationTarget() const override; public virtual folly::dynamic getDiffProps(const facebook::react::Props* prevProps) const override; } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 4a7b602e202b..c72fde24fa7a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4805,9 +4805,9 @@ struct facebook::react::VirtualViewEventEmitter::OnModeChangeThresholdRect { class facebook::react::VirtualViewProps : public facebook::react::HostPlatformViewProps { public VirtualViewProps() = default; public VirtualViewProps(const facebook::react::PropsParserContext& context, const facebook::react::VirtualViewProps& sourceProps, const facebook::react::RawProps& rawProps); - public bool initialHidden; public bool removeClippedSubviews; public int renderState; + public std::optional initialHidden; } class facebook::react::YogaLayoutableShadowNode : public facebook::react::LayoutableShadowNode { diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api index cb120cc274d0..581d63c6bd06 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -6608,9 +6608,9 @@ struct facebook::react::VirtualViewEventEmitter::OnModeChangeThresholdRect { class facebook::react::VirtualViewProps : public facebook::react::HostPlatformViewProps { public VirtualViewProps() = default; public VirtualViewProps(const facebook::react::PropsParserContext& context, const facebook::react::VirtualViewProps& sourceProps, const facebook::react::RawProps& rawProps); - public bool initialHidden; public bool removeClippedSubviews; public int renderState; + public std::optional initialHidden; } class facebook::react::YogaLayoutableShadowNode : public facebook::react::LayoutableShadowNode { diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 8a11f3569d73..45a49eced6b8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -4790,9 +4790,9 @@ struct facebook::react::VirtualViewEventEmitter::OnModeChangeThresholdRect { class facebook::react::VirtualViewProps : public facebook::react::HostPlatformViewProps { public VirtualViewProps() = default; public VirtualViewProps(const facebook::react::PropsParserContext& context, const facebook::react::VirtualViewProps& sourceProps, const facebook::react::RawProps& rawProps); - public bool initialHidden; public bool removeClippedSubviews; public int renderState; + public std::optional initialHidden; } class facebook::react::YogaLayoutableShadowNode : public facebook::react::LayoutableShadowNode { diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index aa1ff6a9eb94..da01279f4f8c 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4798,9 +4798,9 @@ struct facebook::react::VirtualViewEventEmitter::OnModeChangeThresholdRect { class facebook::react::VirtualViewProps : public facebook::react::HostPlatformViewProps { public VirtualViewProps() = default; public VirtualViewProps(const facebook::react::PropsParserContext& context, const facebook::react::VirtualViewProps& sourceProps, const facebook::react::RawProps& rawProps); - public bool initialHidden; public bool removeClippedSubviews; public int renderState; + public std::optional initialHidden; } class facebook::react::YogaLayoutableShadowNode : public facebook::react::LayoutableShadowNode {