From bd322e9d15626b5f4a9933580cee8ed8c70cf3fe Mon Sep 17 00:00:00 2001
From: Zeya Peng <zeyap@meta.com>
Date: Wed, 24 Jun 2026 06:08:32 -0700
Subject: [PATCH] Disable single-op batching under C++ native animated,
 otherwise enable by default (#57316)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Summary:

Single-op batching (`queueAndExecuteBatchedOperations`) is incompatible with the C++ native animated backend. Move that guard out of per-product feature-flag overrides and into `NativeAnimatedHelper`: `isSingleOpBatching` is now gated on `Platform.OS === 'android'`, `queueAndExecuteBatchedOperations` being available, and `!cxxNativeAnimatedEnabled()`. With the invariant centralized, the redundant `animatedShouldUseSingleOp` overrides in the panel-app and igvr override files are removed (igvr keeps a passthrough override file wired into IGVRPrelude).

**Single-op batching is most meaningful only on Android**: it collapses many per-operation JNI crossings into a single call, a significant win on the legacy bridge path. On other platforms (and on the C++/JSI path) the calls do not cross JNI, so batching would help performance only marginally while hurting debuggability — the ops are packed into an opaque serialized buffer and callbacks are rerouted through the device event emitter. That trade-off is why it stays Android-only and off under C++ native animated.

Behavior note: with C++ native animated enabled (the default), this is a no-op — single-op was already forced off. Where C++ native animated is disabled on Android, single-op is now on by default (it was previously gated by the `animatedShouldUseSingleOp` flag, which defaulted to off).

Changelog:[Internal]

Reviewed By: christophpurrer

Differential Revision: D109468772
---
 .../scripts/featureflags/ReactNativeFeatureFlags.config.js    | 2 +-
 .../react-native/src/private/animated/NativeAnimatedHelper.js | 3 ++-
 .../src/private/featureflags/ReactNativeFeatureFlags.js       | 4 ++--
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js
index 1ac72cc86ee0..7519e7d58989 100644
--- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js
+++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js
@@ -993,7 +993,7 @@ const definitions: FeatureFlagDefinitions = {
       ossReleaseStage: 'none',
     },
     animatedShouldUseSingleOp: {
-      defaultValue: false,
+      defaultValue: true,
       metadata: {
         dateAdded: '2024-02-05',
         description:
diff --git a/packages/react-native/src/private/animated/NativeAnimatedHelper.js b/packages/react-native/src/private/animated/NativeAnimatedHelper.js
index 74a76280692a..eaaefb5b9a0b 100644
--- a/packages/react-native/src/private/animated/NativeAnimatedHelper.js
+++ b/packages/react-native/src/private/animated/NativeAnimatedHelper.js
@@ -56,7 +56,8 @@ const singleOpQueue: Array<unknown> = [];
 const isSingleOpBatching =
   Platform.OS === 'android' &&
   NativeAnimatedModule?.queueAndExecuteBatchedOperations != null &&
-  ReactNativeFeatureFlags.animatedShouldUseSingleOp();
+  ReactNativeFeatureFlags.animatedShouldUseSingleOp() &&
+  !ReactNativeFeatureFlags.cxxNativeAnimatedEnabled();
 let flushQueueImmediate = null;
 
 const eventListenerGetValueCallbacks: {
diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js
index ce445d0dc309..918b6b821aa1 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<<c9804d0743d9a9d905193cb02816da46>>
+ * @generated SignedSource<<f44477b5f5a4f7030798a61694d0332a>>
  * @flow strict
  * @noformat
  */
@@ -164,7 +164,7 @@ export const animatedShouldSyncValueBeforeStartCallback: Getter<boolean> = creat
 /**
  * Enables an experimental mega-operation for Animated.js that replaces many calls to native with a single call into native, to reduce JSI/JNI traffic.
  */
-export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGetter('animatedShouldUseSingleOp', false);
+export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGetter('animatedShouldUseSingleOp', true);
 
 /**
  * Use the deferred cell render update mechanism for focus change in FlatList.
