Skip to content

scrollable={true} + inner <ScrollView> content becomes invisible after tree-wide re-render (e.g. theme switch), requires JS reload #686

@davidecallegaro

Description

@davidecallegaro

Summary

When <TrueSheet scrollable={true}> wraps a child <ScrollView>, the ScrollView's content view becomes invisible after any tree-wide React re-render (e.g. theme/appearance change, locale switch) that happens while the sheet is dismissed. On the next present(), the sheet displays normally but the in-flow content inside the ScrollView is missing. Only sibling absolute-positioned views render. A full JS bundle reload restores it.

Visual repro

Step 1: open the sheet (works correctly). Outer View (red bg) + ScrollView (translucent blue overlay = magenta) + hardcoded yellow DEBUG text + real content + sibling absolute-positioned chip/X overlay — all visible.

Step 3 → 4: dismiss sheet, flip theme, re-open. Outer View (red) and ScrollView (blue overlay = magenta) still paint their backgrounds → confirms both containers are full-size and mounted. The absolute-positioned chip ("FITNESS") + X overlay render. But every in-flow child of the ScrollView — including the hardcoded yellow <Text> with hardcoded black background — is gone.

Repro code

function MySheet() {
  const sheetRef = useRef<TrueSheet>(null);
  const { mode } = useTheme(); // any context that propagates to many components

  return (
    <>
      <Button onPress={() => sheetRef.current?.present()} title=\"Open\" />
      <TrueSheet ref={sheetRef} detents={[0.65, 1]} scrollable={true}>
        <View style={{ flex: 1, backgroundColor: 'red' }}>
          <ScrollView style={{ flex: 1, backgroundColor: 'rgba(0,0,255,0.4)' }}>
            <Text style={{ color: 'yellow', fontSize: 32, backgroundColor: 'black' }}>
              HELLO
            </Text>
            <Text>Body content here…</Text>
          </ScrollView>
          <View style={{ position: 'absolute', top: 0, left: 0, right: 0 }}>
            <Text>This overlay text always renders correctly.</Text>
          </View>
        </View>
      </TrueSheet>
    </>
  );
}

Steps:

  1. Open the sheet → all content visible (matches screenshot Refactor: Android improvements #1).
  2. Dismiss the sheet.
  3. Trigger a tree-wide re-render — e.g. flip the app theme from light to dark (or anything that re-renders dozens of components).
  4. Open the sheet again → matches screenshot ERROR Invariant Violation: requireNativeComponent: "TrueSheetView" was not found in the UIManager.  #2.

JS reload restores correct behavior immediately.

What I've ruled out

Hypothesis Result
State is null at render time No — outer View renders (magenta visible), so the conditional passed
Text color matches background No — hardcoded yellow text on hardcoded black bg is also invisible
Container collapse to zero height No — both outer View and ScrollView paint their full backgrounds (visible magenta)
Content off-screen (bad contentOffset) No — scrolling the empty area reveals nothing
Stale React ScrollView native instance No — conditionally mounting the entire subtree on a flag (unmount on dismiss, mount fresh on present) does not fix it
TrueSheet's React instance is stale No — key={mode} on <TrueSheet> to force full React remount of TrueSheet itself also doesn't fix it

The only thing that fixes it short of a JS reload is removing the inner <ScrollView> entirely (rendering content directly into a plain <View>). With that, the same content renders correctly after theme switches every time.

Hypothesis

scrollable={true} appears to install some native-side observation/binding on the inner UIScrollView. After tree-wide re-renders that recreate the inner ScrollView's native view, the binding ends up in a state where the content view stays hidden, even though the JS side reports it mounted. Reload reconstructs the entire native sheet, restoring it.

Note: this app runs on the New Architecture (Fabric). On Fabric, native views can be unmounted/recreated more aggressively during reconciliation than on the old bridge. The bug may be specific to Fabric, or simply easier to trigger there — worth confirming whether old-arch users hit the same symptom.

Workaround

Remove the inner <ScrollView>. Lay content directly into a <View>:

<TrueSheet scrollable={true}>
  <View style={{ flex: 1, padding: 16 }}>
    {/* content */}
  </View>
</TrueSheet>

Trade-off: lose in-sheet scrolling for content that overflows the detent height.

Environment

  • @lodev09/react-native-true-sheet: 3.9.9
  • react-native: 0.83.6
  • react: 19.2.0
  • expo: ~55.0.24
  • iOS: 16+ deployment target, tested on iOS 17/18
  • Architecture: New (Fabric)newArchEnabled: true in app.config.js
  • Device: iPhone (physical + simulator both reproduce)

Happy to help

If a maintainer can point me at the Swift code that handles the scrollable binding, I'd be happy to attempt a PR — the symptom strongly suggests the observation isn't being torn down + re-established when the underlying UIScrollView is recreated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions