Skip to content

useNativeDriver: true animations never run on the New Architecture #3079

Description

Environment

react-native -v:          20.0.0   (this is @react-native-community/cli;
                                    react-native itself is 0.81.6)
npm ls react-native-macos: react-native-macos@0.81.9
node -v:                  v22.15.0
npm -v:                   10.9.2
yarn --version:           1.22.19
xcodebuild -version:      Xcode 26.3
                          Build version 17C529

Steps to reproduce the bug

git clone https://github.com/shaneosullivan/react-native-macos-animated-repro.git
cd react-native-macos-animated-repro
npm install
(cd macos && pod install)        # New Architecture is the default in 0.81
npm start                        # Metro, on port 8081
npm run macos                    # in a second terminal

Press Animate under each heading.

Actual — the useNativeDriver: true square never moves. Its status stays
running… (callback has not fired) indefinitely. The useNativeDriver: false square
slides across and reports ✅ completed.

Expected Behavior

Expected — both squares slide across and both report completion.

The control case

(cd macos && RCT_NEW_ARCH_ENABLED=0 pod install)
npm run macos

Both squares now animate and both complete. Only the New Architecture is affected.

Re-run (cd macos && pod install) to return the project to the New Architecture.

Actual Behavior

The native driver based animated box does not move, and the legacy driver based box does move

RN_MacOS_Animated_Native_Repro.mov

Reproducible Demo

https://github.com/shaneosullivan/react-native-macos-animated-repro

Additional context

Cause

Libraries/Animated/shouldUseTurboAnimatedModule.js:

function shouldUseTurboAnimatedModule(): boolean {
  if (ReactNativeFeatureFlags.cxxNativeAnimatedEnabled()) {
    return false;
  } else {
    return Platform.OS === 'ios' && global.RN$Bridgeless === true;
  }
}

Platform.OS is 'macos', so this returns false on every macOS build. That value
decides which native module the animated system talks to:

  • Libraries/Animated/NativeAnimatedTurboModule.js returns null unless the gate is true.
  • Libraries/Animated/NativeAnimatedModule.js returns TurboModuleRegistry.get('NativeAnimatedModule')
    when it is false.

So a bridgeless macOS app selects the legacy RCTNativeAnimatedModule. That module is
explicitly not bridgeless compatible — its own setSurfacePresenter: logs "should only be
invoked in RCTNativeAnimatedTurboModule"
— and it drains its queued operations from the
bridge's UIManager batch callbacks. With no bridge those callbacks never arrive, so every
operation sits in the queue forever. RCTNativeAnimatedTurboModule is never instantiated
at all (confirmed by adding an NSLog to its initialize and setSurfacePresenter:
neither fires).

Both classes are registered under distinct names in
Libraries/NativeAnimation/RCTAnimationPlugins.mm, so the TurboModule is available; it is
only the JS-side gate that never selects it.

The app shows the computed value of that condition on screen, so you can see the selection
being made without instrumenting anything.

Suggested fix

Add 'macos' to the platform check:

return (
  (Platform.OS === 'ios' || Platform.OS === 'macos') &&
  global.RN$Bridgeless === true
);

Verified as a patch-package patch against 0.81.7 in a production app: with it applied,
RCTNativeAnimatedTurboModule is created with its surface presenter set, mount callbacks
flush the operation queue, and native-driven animations run normally on the New
Architecture.

Impact

Silent and app-wide. Every useNativeDriver: true call site stops working with no
diagnostic of any kind — and since these are visual failures rather than exceptions, they
pass builds, type checks and test suites unnoticed. In the app where this was found it
disabled map zoom, slideshow transitions, a pager and a video player's controls
simultaneously, all without a single log line.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions