You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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 legacyRCTNativeAnimatedModule. 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.
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.
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 17C529Steps to reproduce the bug
Press Animate under each heading.
Actual — the
useNativeDriver: truesquare never moves. Its status staysrunning… (callback has not fired)indefinitely. TheuseNativeDriver: falsesquareslides 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 macosBoth 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:Platform.OSis'macos', so this returnsfalseon every macOS build. That valuedecides which native module the animated system talks to:
Libraries/Animated/NativeAnimatedTurboModule.jsreturnsnullunless the gate is true.Libraries/Animated/NativeAnimatedModule.jsreturnsTurboModuleRegistry.get('NativeAnimatedModule')when it is false.
So a bridgeless macOS app selects the legacy
RCTNativeAnimatedModule. That module isexplicitly not bridgeless compatible — its own
setSurfacePresenter:logs "should only beinvoked 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.
RCTNativeAnimatedTurboModuleis never instantiatedat all (confirmed by adding an
NSLogto itsinitializeandsetSurfacePresenter:—neither fires).
Both classes are registered under distinct names in
Libraries/NativeAnimation/RCTAnimationPlugins.mm, so the TurboModule is available; it isonly 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:Verified as a
patch-packagepatch against 0.81.7 in a production app: with it applied,RCTNativeAnimatedTurboModuleis created with its surface presenter set, mount callbacksflush the operation queue, and native-driven animations run normally on the New
Architecture.
Impact
Silent and app-wide. Every
useNativeDriver: truecall site stops working with nodiagnostic 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.