Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions packages/react-native/Libraries/Pressability/usePressability.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,15 @@
* @format
*/

import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import Pressability, {
type EventHandlers,
type PressabilityConfig,
} from './Pressability';
import {useEffect, useInsertionEffect, useRef} from 'react';
import {useInsertionEffect, useRef} from 'react';

declare function usePressability(config: PressabilityConfig): EventHandlers;
declare function usePressability(config: null | void): null | EventHandlers;

// Experiments with using `useInsertionEffect` instead of `useEffect`, which
// changes whether `Pressability` is configured or reset when inm a hidden
// Activity. With `useInsertionEffect`, `Pressability` behaves more like a
// platform control (e.g. Pointer Events), especially with respect to events
// like focus and blur.
const useConfigurationEffect =
ReactNativeFeatureFlags.configurePressabilityDuringInsertion()
? useInsertionEffect
: useEffect;

/**
* Creates a persistent instance of `Pressability` that automatically configures
* itself and resets. Accepts null `config` to support lazy initialization. Once
Expand All @@ -51,15 +40,15 @@ export default function usePressability(

// On the initial mount, this is a no-op. On updates, `pressability` will be
// re-configured to use the new configuration.
useConfigurationEffect(() => {
useInsertionEffect(() => {
if (config != null && pressability != null) {
pressability.configure(config);
}
}, [config, pressability]);

// On unmount, reset pending state and timers inside `pressability`. This is
// a separate effect because we do not want to reset when `config` changes.
useConfigurationEffect(() => {
useInsertionEffect(() => {
if (pressability != null) {
return () => {
pressability.reset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -989,17 +989,6 @@ const definitions: FeatureFlagDefinitions = {
},
ossReleaseStage: 'none',
},
configurePressabilityDuringInsertion: {
defaultValue: false,
metadata: {
dateAdded: '2025-10-27',
description:
'Configure Pressability during insertion and no longer unmount when hidden.',
expectedReleaseValue: true,
purpose: 'experimentation',
},
ossReleaseStage: 'none',
},
deferFlatListFocusChangeRenderUpdate: {
defaultValue: false,
metadata: {
Expand All @@ -1022,16 +1011,6 @@ const definitions: FeatureFlagDefinitions = {
},
ossReleaseStage: 'none',
},
enableVirtualViewExperimental: {
defaultValue: false,
metadata: {
dateAdded: '2025-08-29',
description: 'Enables the experimental version of `VirtualView`.',
expectedReleaseValue: true,
purpose: 'experimentation',
},
ossReleaseStage: 'none',
},
fixVirtualizeListCollapseWindowSize: {
defaultValue: false,
metadata: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
import type {ViewStyleProp} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {NativeSyntheticEvent} from '../../../../Libraries/Types/CoreEventTypes';
import type {HostInstance} from '../../types/HostInstance';
import type {NativeModeChangeEvent} from './VirtualViewNativeComponent';
import type {NativeModeChangeEvent} from './VirtualViewExperimentalNativeComponent';

import StyleSheet from '../../../../Libraries/StyleSheet/StyleSheet';
import * as ReactNativeFeatureFlags from '../../featureflags/ReactNativeFeatureFlags';
import {useVirtualViewLogging} from './logger/VirtualViewLogger';
import VirtualViewExperimentalNativeComponent from './VirtualViewExperimentalNativeComponent';
import VirtualViewClassicNativeComponent from './VirtualViewNativeComponent';
import VirtualViewNativeComponent from './VirtualViewExperimentalNativeComponent';
import nullthrows from 'nullthrows';
import * as React from 'react';
// $FlowFixMe[missing-export]
Expand Down Expand Up @@ -51,11 +50,6 @@ export type ModeChangeEvent = $ReadOnly<{
target: HostInstance,
}>;

const VirtualViewNativeComponent: typeof VirtualViewClassicNativeComponent =
ReactNativeFeatureFlags.enableVirtualViewExperimental()
? VirtualViewExperimentalNativeComponent
: VirtualViewClassicNativeComponent;

type VirtualViewComponent = component(
children?: React.Node,
hiddenStyle?: (targetRect: Rect) => ViewStyleProp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ type VirtualViewExperimentalNativeProps = $ReadOnly<{
*/
initialHidden?: boolean,

/**
* This was needed to get VirtualViewManagerDelegate to set this property.
* TODO: Investigate why spread ViewProps doesn't call setter
*/
removeClippedSubviews?: boolean,

/**
* Render state of children.
*
Expand All @@ -79,18 +85,13 @@ type VirtualViewExperimentalNativeProps = $ReadOnly<{
*/
renderState: Int32,

/**
* This was needed to get VirtualViewManagerDelegate to set this property.
* TODO: Investigate why spread ViewProps doesn't call setter
*/
removeClippedSubviews?: boolean,

/**
* See `NativeModeChangeEvent`.
*/
onModeChange?: ?DirectEventHandler<NativeModeChangeEvent>,
}>;

// TODO: Rename to eliminate "Experimental" suffix in the name.
export default codegenNativeComponent<VirtualViewExperimentalNativeProps>(
'VirtualViewExperimental',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';

import type {Rect} from '../VirtualView';
import type {NativeModeChangeEvent} from '../VirtualViewNativeComponent';
import type {NativeModeChangeEvent} from '../VirtualViewExperimentalNativeComponent';

import ensureInstance from '../../../__tests__/utilities/ensureInstance';
import isUnreachable from '../../../__tests__/utilities/isUnreachable';
Expand Down Expand Up @@ -43,16 +43,16 @@ describe('mode changes', () => {

expect(_logs.states).toHaveLength(1);
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
<rn-virtualView>
<rn-virtualViewExperimental>
<rn-paragraph>Child</rn-paragraph>
</rn-virtualView>,
</rn-virtualViewExperimental>,
);

dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden);

expect(_logs.states).toHaveLength(2);
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
<rn-virtualView />,
<rn-virtualViewExperimental />,
);
});

Expand All @@ -72,16 +72,16 @@ describe('mode changes', () => {

expect(_logs.states).toHaveLength(2);
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
<rn-virtualView />,
<rn-virtualViewExperimental />,
);

dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Visible);

expect(_logs.states).toHaveLength(3);
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
<rn-virtualView>
<rn-virtualViewExperimental>
<rn-paragraph>Child</rn-paragraph>
</rn-virtualView>,
</rn-virtualViewExperimental>,
);
});

Expand All @@ -101,19 +101,19 @@ describe('mode changes', () => {

expect(_logs.states).toHaveLength(1);
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
<rn-virtualView>
<rn-virtualViewExperimental>
<rn-paragraph>Child</rn-paragraph>
</rn-virtualView>,
</rn-virtualViewExperimental>,
);

dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Visible);

// Expects `VirtualView` does not undergo a state update.
expect(_logs.states).toHaveLength(1);
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
<rn-virtualView>
<rn-virtualViewExperimental>
<rn-paragraph>Child</rn-paragraph>
</rn-virtualView>,
</rn-virtualViewExperimental>,
);
});
});
Expand All @@ -128,7 +128,7 @@ describe('styles', () => {

expect(
root.getRenderedOutput({props: ['minHeight', 'minWidth']}).toJSX(),
).toEqual(<rn-virtualView />);
).toEqual(<rn-virtualViewExperimental />);
});

test('does not set styles when prerendered', () => {
Expand All @@ -143,7 +143,7 @@ describe('styles', () => {

expect(
root.getRenderedOutput({props: ['minHeight', 'minWidth']}).toJSX(),
).toEqual(<rn-virtualView />);
).toEqual(<rn-virtualViewExperimental />);
});

test('sets styles when hidden', () => {
Expand All @@ -158,7 +158,12 @@ describe('styles', () => {

expect(
root.getRenderedOutput({props: ['minHeight', 'minWidth']}).toJSX(),
).toEqual(<rn-virtualView minHeight="100.000000" minWidth="100.000000" />);
).toEqual(
<rn-virtualViewExperimental
minHeight="100.000000"
minWidth="100.000000"
/>,
);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<<43e6a2dddd5965011ba166098f90e33f>>
* @generated SignedSource<<b0e0bebf50d1a5b3d1c53447074786fd>>
* @flow strict
* @noformat
*/
Expand All @@ -31,10 +31,8 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
jsOnlyTestFlag: Getter<boolean>,
animatedShouldDebounceQueueFlush: Getter<boolean>,
animatedShouldUseSingleOp: Getter<boolean>,
configurePressabilityDuringInsertion: Getter<boolean>,
deferFlatListFocusChangeRenderUpdate: Getter<boolean>,
disableMaintainVisibleContentPosition: Getter<boolean>,
enableVirtualViewExperimental: Getter<boolean>,
fixVirtualizeListCollapseWindowSize: Getter<boolean>,
isLayoutAnimationEnabled: Getter<boolean>,
reduceDefaultPropsInImage: Getter<boolean>,
Expand Down Expand Up @@ -154,11 +152,6 @@ export const animatedShouldDebounceQueueFlush: Getter<boolean> = createJavaScrip
*/
export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGetter('animatedShouldUseSingleOp', false);

/**
* Configure Pressability during insertion and no longer unmount when hidden.
*/
export const configurePressabilityDuringInsertion: Getter<boolean> = createJavaScriptFlagGetter('configurePressabilityDuringInsertion', false);

/**
* Use the deferred cell render update mechanism for focus change in FlatList.
*/
Expand All @@ -169,11 +162,6 @@ export const deferFlatListFocusChangeRenderUpdate: Getter<boolean> = createJavaS
*/
export const disableMaintainVisibleContentPosition: Getter<boolean> = createJavaScriptFlagGetter('disableMaintainVisibleContentPosition', false);

/**
* Enables the experimental version of `VirtualView`.
*/
export const enableVirtualViewExperimental: Getter<boolean> = createJavaScriptFlagGetter('enableVirtualViewExperimental', false);

/**
* Fixing an edge case where the current window size is not properly calculated with fast scrolling. Window size collapsed to 1 element even if windowSize more than the current amount of elements
*/
Expand Down
Loading