Defer focus cell render mask updates (#52380)

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

Apps that rely support focus in FlatList rendered items are missing out on a FlatList optimization that defers rendering for offscreen content updates.

For example, on Android, if you focus and smooth scroll an item into view, the onScroll event will fire first. For most sufficiently large virtualization windows, the next render will be delayed by the render batch timeout as most materialization of virtualized views is not treated as a high pri render.

However, this batch / timeout mechanism isn't being used for cell render updates that occur as a result of a focus change.

This change adds the same timeout mechanism used for scroll events. In most cases, the view that is focused is in the viewport, and the extra rendering needed is already scheduled (or executed with high priority if needed) when the onScroll event is processed.

In cases where the focus change occurs outside the viewport, most platforms will want to do some kind of "bring into view" anyway, and the same applies - onScroll will take care of scheduling the cell rendering priority.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D77681274

fbshipit-source-id: 1ade377e513eca21338a380ff9299dd410606aec
This commit is contained in:
Eric Rozell
2025-07-03 04:15:41 -07:00
committed by Facebook GitHub Bot
parent 45fd7feb9f
commit 9253fc3b42
3 changed files with 25 additions and 2 deletions
@@ -706,6 +706,17 @@ const definitions: FeatureFlagDefinitions = {
},
ossReleaseStage: 'none',
},
deferFlatListFocusChangeRenderUpdate: {
defaultValue: false,
metadata: {
dateAdded: '2025-07-02',
description:
'Use the deferred cell render update mechanism for focus change in FlatList.',
expectedReleaseValue: true,
purpose: 'experimentation',
},
ossReleaseStage: 'none',
},
disableInteractionManager: {
defaultValue: true,
metadata: {
@@ -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<<670f6eab8583db989988cbf1a28383a2>>
* @generated SignedSource<<b75fccb46a36b07c692d890f0659f9a3>>
* @flow strict
* @noformat
*/
@@ -33,6 +33,7 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
animatedShouldDebounceQueueFlush: Getter<boolean>,
animatedShouldUseSingleOp: Getter<boolean>,
avoidStateUpdateInAnimatedPropsMemo: Getter<boolean>,
deferFlatListFocusChangeRenderUpdate: Getter<boolean>,
disableInteractionManager: Getter<boolean>,
enableAccessToHostTreeInFabric: Getter<boolean>,
fixVirtualizeListCollapseWindowSize: Getter<boolean>,
@@ -135,6 +136,11 @@ export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGe
*/
export const avoidStateUpdateInAnimatedPropsMemo: Getter<boolean> = createJavaScriptFlagGetter('avoidStateUpdateInAnimatedPropsMemo', true);
/**
* Use the deferred cell render update mechanism for focus change in FlatList.
*/
export const deferFlatListFocusChangeRenderUpdate: Getter<boolean> = createJavaScriptFlagGetter('deferFlatListFocusChangeRenderUpdate', false);
/**
* Disables InteractionManager and replaces its scheduler with `setImmediate`.
*/
@@ -64,6 +64,7 @@ import {
View,
findNodeHandle,
} from 'react-native';
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
export type {ListRenderItemInfo, ListRenderItem, Separators};
@@ -1332,7 +1333,12 @@ class VirtualizedList extends StateSafePureComponent<
_onCellFocusCapture = (cellKey: string) => {
this._lastFocusedCellKey = cellKey;
this._updateCellsToRender();
if (ReactNativeFeatureFlags.deferFlatListFocusChangeRenderUpdate()) {
// Schedule the cells to render update the same way we handle scroll or layout events.
this._scheduleCellsToRenderUpdate();
} else {
this._updateCellsToRender();
}
};
_onCellUnmount = (cellKey: string) => {