From 9253fc3b420d19734fe476e19be3f3f504cac91c Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Thu, 3 Jul 2025 04:15:41 -0700 Subject: [PATCH] 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 --- .../featureflags/ReactNativeFeatureFlags.config.js | 11 +++++++++++ .../private/featureflags/ReactNativeFeatureFlags.js | 8 +++++++- packages/virtualized-lists/Lists/VirtualizedList.js | 8 +++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index c344aadab61..74e6001241f 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -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: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 9989d78d7dd..f5720762666 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -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<> * @flow strict * @noformat */ @@ -33,6 +33,7 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{ animatedShouldDebounceQueueFlush: Getter, animatedShouldUseSingleOp: Getter, avoidStateUpdateInAnimatedPropsMemo: Getter, + deferFlatListFocusChangeRenderUpdate: Getter, disableInteractionManager: Getter, enableAccessToHostTreeInFabric: Getter, fixVirtualizeListCollapseWindowSize: Getter, @@ -135,6 +136,11 @@ export const animatedShouldUseSingleOp: Getter = createJavaScriptFlagGe */ export const avoidStateUpdateInAnimatedPropsMemo: Getter = createJavaScriptFlagGetter('avoidStateUpdateInAnimatedPropsMemo', true); +/** + * Use the deferred cell render update mechanism for focus change in FlatList. + */ +export const deferFlatListFocusChangeRenderUpdate: Getter = createJavaScriptFlagGetter('deferFlatListFocusChangeRenderUpdate', false); + /** * Disables InteractionManager and replaces its scheduler with `setImmediate`. */ diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index d9764aa0ace..5b8aaa525bd 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -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) => {