VirtualView: Create Activity Experiment (#53488)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53488

Creates a new feature flag to experiment with `Activity` in `VirtualView`.

The feature flag enables the following treatments:

- `no-activity` is the same as what we currently do — no `Activity` and we render `null` for hidden elements.
- `activity-without-mode` wraps the children in `Activity` but does not set `mode` and still renders `null` for hidden elements.
- `activity-with-hidden-mode` wraps the children in `Activity` and sets `mode="hidden"` and continues providing `children` (not `null`) for hidden elements.

Changelog:
[Internal]

Reviewed By: rickhanlonii

Differential Revision: D81149561

fbshipit-source-id: ea2c319139962de30836d80a2492d8147cbe82ba
This commit is contained in:
Tim Yung
2025-08-27 21:26:52 -07:00
committed by Facebook GitHub Bot
parent 502325fbce
commit 72158fc13a
4 changed files with 32 additions and 3 deletions
@@ -914,6 +914,16 @@ const definitions: FeatureFlagDefinitions = {
},
ossReleaseStage: 'none',
},
virtualViewActivityBehavior: {
defaultValue: 'no-activity',
metadata: {
dateAdded: '2025-08-27',
description: 'Changes whether and how `VirtualView` uses `Activity`.',
expectedReleaseValue: true,
purpose: 'experimentation',
},
ossReleaseStage: 'none',
},
},
};
@@ -14,11 +14,13 @@ import type {HostInstance} from '../../types/HostInstance';
import type {NativeModeChangeEvent} from './VirtualViewNativeComponent';
import StyleSheet from '../../../../Libraries/StyleSheet/StyleSheet';
import * as ReactNativeFeatureFlags from '../../featureflags/ReactNativeFeatureFlags';
import VirtualViewExperimentalNativeComponent from './VirtualViewExperimentalNativeComponent';
import VirtualViewNativeComponent from './VirtualViewNativeComponent';
import nullthrows from 'nullthrows';
import * as React from 'react';
import {startTransition, useState} from 'react';
// $FlowFixMe[missing-export]
import {startTransition, unstable_Activity as Activity, useState} from 'react';
// @see VirtualViewNativeComponent
export enum VirtualViewMode {
@@ -140,7 +142,17 @@ function createVirtualView(
: style
}
onModeChange={handleModeChange}>
{isHidden ? null : children}
{
match (ReactNativeFeatureFlags.virtualViewActivityBehavior()) {
'activity-without-mode' =>
<Activity>{isHidden ? null : children}</Activity>,
'activity-with-hidden-mode' =>
<Activity mode={isHidden ? 'hidden' : 'visible'}>
{children}
</Activity>,
'no-activity' | _ => isHidden ? null : children,
}
}
</NativeComponent>
);
}
@@ -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<<276b1579eef10e63c77752017b97985f>>
* @generated SignedSource<<85a053d6a79240a7b4e181d3054ecfd3>>
* @flow strict
* @noformat
*/
@@ -41,6 +41,7 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
shouldUseAnimatedObjectForTransform: Getter<boolean>,
shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean>,
shouldUseSetNativePropsInFabric: Getter<boolean>,
virtualViewActivityBehavior: Getter<string>,
}>;
export type ReactNativeFeatureFlagsJsOnlyOverrides = OverridesFor<ReactNativeFeatureFlagsJsOnly>;
@@ -184,6 +185,11 @@ export const shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean> = cre
*/
export const shouldUseSetNativePropsInFabric: Getter<boolean> = createJavaScriptFlagGetter('shouldUseSetNativePropsInFabric', true);
/**
* Changes whether and how `VirtualView` uses `Activity`.
*/
export const virtualViewActivityBehavior: Getter<string> = createJavaScriptFlagGetter('virtualViewActivityBehavior', "no-activity");
/**
* Common flag for testing. Do NOT modify.
*/
@@ -61,6 +61,7 @@ export function createJavaScriptFlagGetter<
configName,
() => {
accessedFeatureFlags.add(configName);
// $FlowFixMe[incompatible-type] - `defaultValue` is not refined.
return overrides?.[configName]?.(defaultValue);
},
defaultValue,