Skip to content

[Android] View recycling reuses a view that still has a parent: "The specified child already has a parent" #59863

Open

Description

@mozzius

Description

With enableViewRecycling on, ViewManager.onDropViewInstance pools a view even when view.getParent() != null. A removed view keeps its parent when the parent called ViewGroup.startViewTransition(child) first, as exit animations do, and if that parent leaves the window before calling endViewTransition, dispatchDetachedFromWindow empties its disappearing children without clearing the child's parent. When the pool hands that view out again, addViewAt crashes with "The specified child already has a parent". #59776 doesn't help once the old parent has left the window, because endViewTransition only clears the parent of a view that is still one of its disappearing children. We hit this through react-native-screens, which starts a transition on every view in a screen it pops but never ends the ones below the screen (software-mansion/react-native-screens#3249). We ship a one-line fix in onDropViewInstance: don't pool the view when view.getParent() != null.

Steps to reproduce

  1. Clone https://github.com/mozzius/view-recycling-transition-repro, then cd ReproducerApp && yarn install && yarn android --mode release.
  2. Press Run. It removes a child from TransitionContainer (a small native container that calls startViewTransition before removing a child and endViewTransition 1 s later), removes the container 300 ms later, and mounts new views 300 ms after that.
  3. With enableViewRecycling on (the default; the switch at the top toggles it and restarts the app), the app crashes. With it off, the new views appear.

React Native Version

0.90.0-nightly-20261108-f480836ad

Affected Platforms

Runtime - Android, Build - MacOS

Areas

Fabric - The New Renderer

Output of npx @react-native-community/cli info

System:
  OS: macOS 27.2
  CPU: (14) arm64 Apple M4 Pro
  Memory: 110.41 MB / 48.00 GB
  Shell:
    version: 5.3.20
    path: /opt/homebrew/bin/bash
Binaries:
  Node:
    version: 24.22.1
    path: /Users/samuel/.nvm/versions/node/v24.22.1/bin/node
  Yarn:
    version: 1.22.22
    path: /Users/samuel/.nvm/versions/node/v24.22.1/bin/yarn
  npm:
    version: 11.19.1
    path: /Users/samuel/.nvm/versions/node/v24.22.1/bin/npm
  Watchman:
    version: 2026.10.26.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.17.0
    path: /Users/samuel/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 27.2
      - iOS 27.2
      - macOS 27.2
      - tvOS 27.2
      - visionOS 27.2
      - watchOS 27.2
  Android SDK:
    API Levels:
      - "29"
      - "33"
      - "34"
      - "35"
      - "36"
      - "37"
    Build Tools:
      - 30.0.3
      - 34.0.0
      - 35.0.0
      - 35.0.1
      - 36.0.0
      - 37.0.0
    System Images:
      - android-28 | Google ARM64-V8a Play ARM 64 v8a
      - android-29 | Google Play ARM 64 v8a
      - android-30 | Google APIs ARM 64 v8a
      - android-34 | Google Play ARM 64 v8a
      - android-35 | Google Play ARM 64 v8a
      - android-35 | Google Play Tablet ARM 64 v8a
      - android-36 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2026.1 AI-261.26222.65.2614.16379836
  Xcode:
    version: 27.2/27B52
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.21.0.3
    path: /usr/bin/javac
  Ruby:
    version: 2.7.6
    path: /Users/samuel/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 21.0.0-alpha.1
    wanted: 21.0.0-alpha.1
  react:
    installed: 19.3.0
    wanted: 19.3.0
  react-native:
    installed: 0.90.0-nightly-20261108-f480836ad
    wanted: 0.90.0-nightly-20261108-f480836ad
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: Not found
  newArchEnabled: false

Stacktrace or Logs

E SurfaceMountingManager: java.lang.IllegalStateException: addViewAt: cannot insert view [54] into parent [1]: View already has a parent: [24] Parent: TransitionContainer View: ReactViewGroup
FATAL EXCEPTION: main
java.lang.IllegalStateException: addViewAt: failed to insert view [54] into parent [1] at index 13
	at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.kt:376)
	at com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem.execute(IntBufferBatchMountItem.kt:122)
	at com.facebook.react.fabric.mounting.MountItemDispatcher.executeOrEnqueue(MountItemDispatcher.kt:438)
	at com.facebook.react.fabric.mounting.MountItemDispatcher.dispatchMountItems$lambda$4(MountItemDispatcher.kt:324)
	at com.facebook.react.fabric.mounting.MountItemDispatcher$$ExternalSyntheticLambda4.invoke(D8$$SyntheticClass:0)
	at com.facebook.react.internal.tracing.PerformanceTracer.trace(PerformanceTracer.kt:46)
	at com.facebook.react.internal.tracing.PerformanceTracer.trace(PerformanceTracer.kt:35)
	at com.facebook.react.fabric.mounting.MountItemDispatcher.dispatchMountItems(MountItemDispatcher.kt:295)
	at com.facebook.react.fabric.mounting.MountItemDispatcher.tryDispatchMountItems(MountItemDispatcher.kt:131)
	at com.facebook.react.fabric.FabricUIManager$DispatchUIFrameCallback.doFrameGuarded(FabricUIManager.java:1737)
	at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.kt:42)
	at com.facebook.react.modules.core.ReactChoreographer.frameCallback$lambda$0(ReactChoreographer.kt:59)
	at com.facebook.react.modules.core.ReactChoreographer$$ExternalSyntheticLambda0.doFrame(D8$$SyntheticClass:0)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1568)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1579)
	at android.view.Choreographer.doCallbacks(Choreographer.java:1179)
	at android.view.Choreographer.doFrame(Choreographer.java:1104)
	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1553)
	at android.os.Handler.handleCallback(Handler.java:995)
	at android.os.Handler.dispatchMessage(Handler.java:103)
	at android.os.Looper.loopOnce(Looper.java:248)
	at android.os.Looper.loop(Looper.java:338)
	at android.app.ActivityThread.main(ActivityThread.java:9067)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
	at android.view.ViewGroup.addViewInner(ViewGroup.java:5289)
	at android.view.ViewGroup.addView(ViewGroup.java:5118)
	at android.view.ViewGroup.addView(ViewGroup.java:5058)
	at com.facebook.react.uimanager.ViewGroupManager.addView(ViewGroupManager.kt:29)
	at com.facebook.react.uimanager.ViewGroupManager.addView(ViewGroupManager.kt:15)
	at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.kt:373)
	... 25 more

MANDATORY Reproducer

https://github.com/mozzius/view-recycling-transition-repro

Screenshots and Videos

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions