diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 83e3bc479c3..f0d37029cc8 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -9856,7 +9856,6 @@ exports[`public API should not change unintentionally src/private/featureflags/R animatedShouldDebounceQueueFlush: Getter, animatedShouldUseSingleOp: Getter, disableInteractionManager: Getter, - disableInteractionManagerInBatchinator: Getter, enableAccessToHostTreeInFabric: Getter, enableAnimatedAllowlist: Getter, enableAnimatedClearImmediateFix: Getter, @@ -9927,7 +9926,6 @@ declare export const jsOnlyTestFlag: Getter; declare export const animatedShouldDebounceQueueFlush: Getter; declare export const animatedShouldUseSingleOp: Getter; declare export const disableInteractionManager: Getter; -declare export const disableInteractionManagerInBatchinator: Getter; declare export const enableAccessToHostTreeInFabric: Getter; declare export const enableAnimatedAllowlist: Getter; declare export const enableAnimatedClearImmediateFix: Getter; diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index cea890a020f..1c450e8ca46 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -529,16 +529,6 @@ const definitions: FeatureFlagDefinitions = { purpose: 'experimentation', }, }, - disableInteractionManagerInBatchinator: { - defaultValue: false, - metadata: { - dateAdded: '2024-11-18', - description: - 'Skips InteractionManager in `Batchinator` and invokes callbacks synchronously.', - expectedReleaseValue: true, - purpose: 'experimentation', - }, - }, enableAccessToHostTreeInFabric: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index f34c902e3ba..21a50767d7b 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<<8ab1581a5660c8148c1ff87e12c042cc>> + * @generated SignedSource<> * @flow strict */ @@ -31,7 +31,6 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{ animatedShouldDebounceQueueFlush: Getter, animatedShouldUseSingleOp: Getter, disableInteractionManager: Getter, - disableInteractionManagerInBatchinator: Getter, enableAccessToHostTreeInFabric: Getter, enableAnimatedAllowlist: Getter, enableAnimatedClearImmediateFix: Getter, @@ -120,11 +119,6 @@ export const animatedShouldUseSingleOp: Getter = createJavaScriptFlagGe */ export const disableInteractionManager: Getter = createJavaScriptFlagGetter('disableInteractionManager', false); -/** - * Skips InteractionManager in `Batchinator` and invokes callbacks synchronously. - */ -export const disableInteractionManagerInBatchinator: Getter = createJavaScriptFlagGetter('disableInteractionManagerInBatchinator', false); - /** * Enables access to the host tree in Fabric using DOM-compatible APIs. */ diff --git a/packages/virtualized-lists/Interaction/Batchinator.js b/packages/virtualized-lists/Interaction/Batchinator.js deleted file mode 100644 index f45d22ab23e..00000000000 --- a/packages/virtualized-lists/Interaction/Batchinator.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ - -import {InteractionManager} from 'react-native'; -import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags'; - -/** - * A simple class for batching up invocations of a low-pri callback. A timeout is set to run the - * callback once after a delay, no matter how many times it's scheduled. Once the delay is reached, - * InteractionManager.runAfterInteractions is used to invoke the callback after any hi-pri - * interactions are done running. - * - * Make sure to cleanup with dispose(). Example: - * - * class Widget extends React.Component { - * _batchedSave: new Batchinator(() => this._saveState, 1000); - * _saveSate() { - * // save this.state to disk - * } - * componentDidUpdate() { - * this._batchedSave.schedule(); - * } - * componentWillUnmount() { - * this._batchedSave.dispose(); - * } - * ... - * } - */ -class Batchinator { - _callback: () => void; - _delay: number; - _taskHandle: ?{cancel: () => void, ...}; - - constructor(callback: () => void, delay: number) { - this._delay = delay; - this._callback = callback; - } - - /* - * Cleanup any pending tasks. - * - * By default, if there is a pending task the callback is run immediately. Set the option abort to - * true to not call the callback if it was pending. - */ - dispose(): void { - if (this._taskHandle) { - this._taskHandle.cancel(); - this._taskHandle = null; - } - } - - schedule(): void { - if (this._taskHandle) { - return; - } - const invokeCallback = () => { - // Note that we clear the handle before invoking the callback so that if the callback calls - // schedule again, it will actually schedule another task. - this._taskHandle = null; - this._callback(); - }; - - const timeoutHandle = setTimeout( - // NOTE: When shipping this, delete `Batchinator` instead of only these - // lines of code. Without `InteractionManager`, it's just a `setTimeout`. - ReactNativeFeatureFlags.disableInteractionManagerInBatchinator() - ? invokeCallback - : () => { - this._taskHandle = - InteractionManager.runAfterInteractions(invokeCallback); - }, - this._delay, - ); - this._taskHandle = {cancel: () => clearTimeout(timeoutHandle)}; - } -} - -module.exports = Batchinator; diff --git a/packages/virtualized-lists/Interaction/__tests__/Batchinator-test.js b/packages/virtualized-lists/Interaction/__tests__/Batchinator-test.js deleted file mode 100644 index 118c0735892..00000000000 --- a/packages/virtualized-lists/Interaction/__tests__/Batchinator-test.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @oncall react_native - */ - -'use strict'; - -describe('Batchinator', () => { - const Batchinator = require('../Batchinator'); - - it('executes vanilla tasks', () => { - const callback = jest.fn(); - const batcher = new Batchinator(callback, 10000); - batcher.schedule(); - jest.runAllTimers(); - expect(callback).toHaveBeenCalledTimes(1); - }); - - it('batches up tasks', () => { - const callback = jest.fn(); - const batcher = new Batchinator(callback, 10000); - batcher.schedule(); - batcher.schedule(); - batcher.schedule(); - batcher.schedule(); - expect(callback).not.toHaveBeenCalled(); - jest.runAllTimers(); - expect(callback).toHaveBeenCalledTimes(1); - }); - - it('does nothing after dispose', () => { - const callback = jest.fn(); - const batcher = new Batchinator(callback, 10000); - batcher.schedule(); - batcher.schedule(); - batcher.dispose(); - expect(callback).not.toHaveBeenCalled(); - jest.runAllTimers(); - expect(callback).not.toHaveBeenCalled(); - }); - - it('should call tasks scheduled by the callback', () => { - let batcher = null; - let hasRescheduled = false; - const callback = jest.fn(() => { - if (!hasRescheduled) { - batcher.schedule(); - hasRescheduled = true; - } - }); - batcher = new Batchinator(callback, 10000); - batcher.schedule(); - jest.runAllTimers(); - expect(callback.mock.calls.length).toBe(2); - }); - - it('does not run callbacks more than once', () => { - const callback = jest.fn(); - const batcher = new Batchinator(callback, 10000); - batcher.schedule(); - batcher.schedule(); - jest.runAllTimers(); - expect(callback).toHaveBeenCalledTimes(1); - jest.runAllTimers(); - expect(callback).toHaveBeenCalledTimes(1); - batcher.dispose(); - expect(callback).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index 43632c60cdd..17a9b90c349 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -24,7 +24,6 @@ import type { ScrollEvent, } from 'react-native/Libraries/Types/CoreEventTypes'; -import Batchinator from '../Interaction/Batchinator'; import clamp from '../Utilities/clamp'; import infoLog from '../Utilities/infoLog'; import {CellRenderMask} from './CellRenderMask'; @@ -375,10 +374,6 @@ class VirtualizedList extends StateSafePureComponent { this._checkProps(props); this._fillRateHelper = new FillRateHelper(this._listMetrics); - this._updateCellsToRenderBatcher = new Batchinator( - this._updateCellsToRender, - this.props.updateCellsBatchingPeriod ?? 50, - ); if (this.props.viewabilityConfigCallbackPairs) { this._viewabilityTuples = this.props.viewabilityConfigCallbackPairs.map( @@ -687,7 +682,7 @@ class VirtualizedList extends StateSafePureComponent { if (this._isNestedWithSameOrientation()) { this.context.unregisterAsNestedChild({ref: this}); } - this._updateCellsToRenderBatcher.dispose(); + clearTimeout(this._updateCellsToRenderTimeoutID); this._viewabilityTuples.forEach(tuple => { tuple.viewabilityHelper.dispose(); }); @@ -1228,7 +1223,7 @@ class VirtualizedList extends StateSafePureComponent { _scrollRef: ?React.ElementRef = null; _sentStartForContentLength = 0; _sentEndForContentLength = 0; - _updateCellsToRenderBatcher: Batchinator; + _updateCellsToRenderTimeoutID: ?TimeoutID = null; _viewabilityTuples: Array = []; /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's @@ -1763,11 +1758,19 @@ class VirtualizedList extends StateSafePureComponent { this._hiPriInProgress = true; // Don't worry about interactions when scrolling quickly; focus on filling content as fast // as possible. - this._updateCellsToRenderBatcher.dispose(); + if (this._updateCellsToRenderTimeoutID != null) { + clearTimeout(this._updateCellsToRenderTimeoutID); + this._updateCellsToRenderTimeoutID = null; + } this._updateCellsToRender(); return; } else { - this._updateCellsToRenderBatcher.schedule(); + if (this._updateCellsToRenderTimeoutID == null) { + this._updateCellsToRenderTimeoutID = setTimeout(() => { + this._updateCellsToRenderTimeoutID = null; + this._updateCellsToRender(); + }, this.props.updateCellsBatchingPeriod ?? 50); + } } }