Skip to content

[Android][Fabric] Position-only layout update dropped for background View under a flattened wrapper when a sibling mounts/unmounts; collapsable={false} variant crashes with RetryableMountingLayerException #57690

Description

@sbaiahmed1

Description

On Android with the New Architecture, when a sibling component mounts or unmounts mid-session above a section shaped like:

<ScrollView>
  {conditionalCard && <Card />}   {/* mounts later, shifting everything below */}<View style={{ marginBottom: 24 }}>          {/* layout-only wrapper → FLATTENED */}
    <Text>Section title</Text>
    <View style={{ backgroundColor: '#eee',    {/* background → real native view */}
                   borderRadius: 16, margin: 16, padding: 24 }}>
      <Text>Empty state text</Text>
    </View>
  </View>
</ScrollView>

…the background-bearing inner View keeps its pre-shift native frame while every surrounding text node receives its correct new frame. Because the stranded view draws later in child order, it paints as a "ghost" box on top of whatever component now occupies its old position, and stays there for the rest of the process lifetime (only a full app restart — fresh native views — clears it). Yoga/shadow layout is correct throughout; only the native mount is stale.

The bug is silent data-corruption-style: the visually covered component looks broken, while the actually-broken view is a sibling section further down, making it very hard to attribute.

Evidence (native hierarchy vs shadow tree)

Captured with the ghost on screen, same instant:

Accessibility / shadow tree (all correct):

Text  "Budget Overview"        screen y≈3139   ✓
Text  "No budgets yet…"        screen y≈3320   ✓
ViewGroup NetWorth card        y 2752..3079    ✓

Native hierarchy (adb exec-out dumpsys activity top), same instant:

[ 90] ReactViewGroup  y=2205..2692  h=487   ← CashFlow card (the newly mounted sibling)
[109] ReactViewGroup  y=2752..3079  h=327   ← NetWorth card       ✓ correct
[117] ReactTextView   y=3139..3200          ← "Budget Overview"   ✓ correct
[118] ReactViewGroup  y=2693..2963  960x270 ← background box      ✗ STALE
[119] ReactTextView   y=3320..3430          ← "No budgets yet…"   ✓ correct

[118]'s correct frame is y=3240..3510 (confirmed after the fix below). Its stale frame is exactly CashFlowCard height (487) + its 60px margin = 547px above the correct one — i.e. its frame from before the sibling mounted. Child order is correct; only the frame update was dropped. Note the box is invisible to accessibility tooling (no a11y node for a background-only view), so a11y-based inspection reports a perfectly healthy screen.

Crash variant

Adding collapsable={false} to the layout-only wrapper (making it a real native view) converts the silent ghost into a hard crash on the same trigger (sibling unmount in this case):

com.facebook.react.bridge.RetryableMountingLayerException: Unable to get view…
  at com.facebook.react.fabric.mounting.SurfaceMountingManager.getVi…
  at com.facebook.react.fabric.mounting.SurfaceMountingManager.updat…
  at com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMou…
  at com.facebook.react.fabric.mounting.mountitems.MountItemDispatcher.executeO…
  …

Same underlying mounting-diff bookkeeping, surfacing loudly instead of silently — which suggests the differ is emitting mutations against view tags the mounting layer doesn't have when the flattening state around this subtree changes.

Steps to reproduce

  1. ScrollView (bug reproduces with plain core ScrollView) containing, in order: several sibling cards, a conditionally rendered card (initially null), then a section shaped as above (flattened layout-only wrapper → background box + texts below it).
  2. Cold start with the conditional card absent.
  3. Trigger a state change that mounts the conditional card (in our app: navigate to a form screen, save a record, navigate back; the section list re-renders and the card mounts).
  4. Scroll to the section below. The background box renders at its pre-mount position, overlapping the sibling above; its texts render at the correct positions.

Reproduced identically on a Pixel 3a emulator (API 35) and a Galaxy S24 Ultra (API 36), debug and release-ish (debugOptimized) builds.

Ruled out (all tested against the same reproduction)

  • Reanimated involvement: no entering/layout animations anywhere in the affected subtree; also reproduced with Reanimated feature flags at defaults.
  • react-native-screens: reproduces on 4.25.2 and 4.26.2.
  • Scroll container: reproduces with react-native-keyboard-controller's scroll view and with core ScrollView; with and without stickyHeaderIndices.
  • boxShadow, BlurView siblings, freezeOnBlur: removed, still reproduces.
  • enableViewCulling is false (default) in this RN version.

Workaround

Remove the flattenable wrapper (return a fragment; move the wrapper's margin onto the background box). With the background box as a direct child of the scroll content, its layout updates apply correctly and the ghost is gone (verified at the native layer: box frame matches shadow tree after repeated mount/unmount cycles). collapsable={false} is not a viable workaround — see crash variant.

Environment

  • react-native: 0.86.0 (New Architecture / Fabric, Hermes)
  • Platform: Android only (API 35 emulator + API 36 physical device); iOS unaffected
  • Expo SDK 57 (dev client, expo run:android); react: 19.2.3; React Compiler enabled
  • npx react-native info available on request; happy to build a standalone repro app if useful

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: AttentionIssues where the author has responded to feedback.Needs: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions