[Fabric] AnimatedPropsRegistry::syncedTags_ grows when views unmount while their animation is still running

Author: finloopCreated Sep 14, 2026Updated Sep 14, 2026
LabelsPlatform: AndroidRepro provided

Description

AnimatedPropsRegistry::syncedTags_ (Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp) keeps growing when animated views unmount while their animation is still running. FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS is on by default.

As far as we can tell, this is what happens:

  1. A component unmounts. markNodeAsRemovable runs, then handleNodeRemovals calls AnimatedPropsRegistry::removeTag, which removes the tag from updatesRegistry_, timestampMap_, syncedTags_ and invalidatedTags_.
  2. The animation is still running. A spring lasts longer than the view, and detaching the style from viewDescriptors takes effect on the UI thread asynchronously. So the UI runtime still sends one or two _updateProps calls for the removed tag.
  3. Those updates put the tag back into updatesRegistry_ and timestampMap_. The node is no longer marked as removable, so nothing removes it again.
  4. collectSettledUpdates treats the entry as settled and adds the tag to syncedTags_. On the next call the registry entry is evicted, but syncedTags_ is kept on purpose, so the tag of the unmounted view stays there for good.

AnimatedPropsRegistry does not override isEmpty(), so _registriesLeakCheck only checks updatesRegistry_ and reports ✅ even while syncedTags_ grows.

Expected: after the views unmount and their animations finish, syncedTags_ goes back to its baseline. Actual: syncedTags_ grows with every mount/unmount cycle and never shrinks.

Measured on main (ad6c58a), Android Release build, with the repro screen below. The sizes come from a debug print added to _registriesLeakCheck:

turn registry timestamps syncedTags invalidatedTags
start 0 0 2 0
25 4 4 14 0
50 0 0 14 0
75 4 4 26 0
100 0 0 42 0
150 0 0 54 0
200 0 0 66 0
200 + 5 s idle 0 0 66 0

The other registries reported by the leak check stay empty. The leak check still shows AnimatedPropsRegistry: ✅.

Possibly related: #9730, which covers a different case (an orphaned updatesRegistry_ entry when the last animated view unmounts and the GC interval stops). In this repro a static Animated.View stays mounted, so the GC interval keeps running and syncedTags_ still grows.

Steps to reproduce

  1. Check out the repro branch: https://github.com/finloop/react-native-reanimated/tree/repro/animated-props-registry-synced-tags-leak. It is current main plus one commit: the SyncedTagsLeakExample screen and a line in _registriesLeakCheck that prints the AnimatedPropsRegistry container sizes.
  2. yarn, then build apps/fabric-example for Android in Release (./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a) and install it.
  3. Open Reanimated examples → syncedTags_ leak repro and press Start.
  4. Each turn mounts 4 views animated with a slow withSpring, unmounts them after 450 ms, and waits 150 ms. Every 25 turns and 5 s after turn 200, the screen and logcat ([SyncedTagsLeak]) show the _registriesLeakCheck output. Watch syncedTags grow.

The core of the screen:

typescript
function SpringBox({ index }: { index: number }) {
  const progress = useSharedValue(0);
  useEffect(() => {
    // slow spring: still running when the view unmounts after 450 ms
    progress.value = withSpring(1, { stiffness: 20, damping: 4 });
  }, [progress]);
  const style = useAnimatedStyle(() => ({
    opacity: 0.3 + 0.7 * progress.value,
    transform: [{ translateX: index * 50 * progress.value }],
  }));
  return <Animated.View style={[styles.box, style]} />;
}

// Turn: mount 4 <SpringBox key={`${turn}-${i}`} />, unmount after 450 ms, pause 150 ms, x200.
// A static <Animated.View /> stays mounted the whole time.

Snack or a link to a repository

https://github.com/finloop/react-native-reanimated/tree/repro/animated-props-registry-synced-tags-leak

Reanimated version

4.7.0-main (main @ ad6c58a)

Worklets version

0.13.0-main

React Native version

0.87.0

Platforms

Android

JavaScript runtime

Hermes

Workflow

React Native CLI

Architecture

New Architecture (Fabric renderer)

Reanimated feature flags

No

React Native release level

Stable

Build type

Release app & production bundle

Device

Android emulator

Host machine

macOS

Device model

Pixel 10 emulator (sdk_gphone16k_arm64), Android 17 (API 37)

Acknowledgements

Yes

Source: software-mansion/react-native-reanimated