Skip to content

fix: hide a sheet hosted in a tab screen when an enclosing stack pushes a screen - #910

Merged
lodev09 merged 5 commits into
lodev09:mainfrom
IjzerenHein:fix/hide-sheet-in-tab-screen-on-stack-push
Oct 13, 2026
Merged

lodev09 merged 5 commits into
lodev09:mainfrom
IjzerenHein:fix/hide-sheet-in-tab-screen-on-stack-push

Conversation

@IjzerenHein

@IjzerenHein IjzerenHein commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A sheet is hidden when its presenter screen is covered by a push and restored when the screen comes back. That only works when the sheet's nearest react-native-screens screen is itself a stack screen. When the sheet lives in a bottom-tab screen and the native stack enclosing the tabs pushes a screen, the sheet stays presented and the pushed screen ends up behind it.

Stack
├── Home (stack screen)
│   └── Tabs
│       └── Loads (tab screen)   ← hosts the TrueSheet
└── Landing (stack screen)       ← pushed while the sheet is open, lands behind it

The two platforms miss this for different reasons:

  • iOS: shouldDismissForScreenTag: compares the presenter against navigationController.topViewController. For a tab screen that navigation controller is the tabs container (RNSContainerNavigationController), where the active tab is always on top, so the check returns NO. The enclosing stack screen's topWillDisappear goes through the parent branch, which only handles a nested stack being removed. The fix also walks the enclosing navigation controllers and dismisses when the presenter is no longer under one of their top view controllers.
  • Android: react-native-screens only dispatches lifecycle events for ScreenStackFragments, so a tab screen never receives topWillDisappear / topWillAppear, and the observer only listens for the nearest screen's tag. The fix also records the enclosing screens' tags and reacts to their events. Stacked sheets inherit them from their parent, like they already inherit the presenter tag. Only the screen that hid the sheet brings it back, so an enclosing screen reappearing doesn't reveal a sheet whose own screen is still covered inside it.

With the fix the sheet is hidden for the push and comes back on return with its content state intact, same as a sheet hosted directly in a stack screen.

The same change also covers two neighbouring cases:

  • A sheet in a stack inside a tab, with the stack enclosing the tabs pushing (both platforms).
  • A sheet in a stack nested in a stack screen, with the enclosing stack pushing (iOS; Android already handled it).

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Test Plan

E2E in the Expo example with the repro screens added in this PR (yarn expo open repro/tabs), on the iOS 26.5 simulator and an Android API 35 emulator, A/B against the parent commit:

Scenario iOS before iOS after Android before Android after
Sheet in a tab, enclosing stack pushes and pops stays over the pushed screen hidden, restored stays over the pushed screen hidden, restored
Sheet in a stack inside a tab, enclosing stack pushes and pops stays over the pushed screen hidden, restored stays over the pushed screen hidden, restored
Sheet in a nested stack, enclosing stack pushes and pops stays over the pushed screen hidden, restored hidden, restored hidden, restored
Sheet in a nested stack, inner push, then enclosing push and pop stays hidden stays hidden stays hidden stays hidden
Back swipe from the pushed screen, cancelled then committed (sheet in a tab) n/a hidden while cancelled, restored on commit n/a n/a

Stacked sheets in a tab hide and come back together on Android.

The tab case was first found and fixed in our production app (3.11.12 with this change applied as a patch, minus the Android restore guard, react-native-screens 4.24.0, @react-navigation/bottom-tabs 7 inside a native stack), on both platforms.

Web does not hide sheets for navigation, so it is not affected.

clang-format and ktlint report no changes for the touched native files. yarn typecheck passes, and ESLint and Prettier are clean for the repro screens.

Two things I noticed while testing that this PR doesn't change, so I left them alone:

  • Android: the first screen of a stack inside a tab doesn't hide its sheet for a push on that inner stack. react-native-screens never dispatches its first topWillAppear, and gates topWillDisappear on it.
  • iOS: with stacked sheets, only the parent comes back after navigating away and returning. The child is not re-presented. This also happens on a plain stack screen, where the code touched here doesn't run.

Screenshots / Videos

Can add before/after captures if useful.

Checklist

  • I tested on iOS
  • I tested on Android
  • I tested on Web (not applicable, see above)
  • I updated the documentation (if needed)
  • I added a changelog entry (if needed)

🤖 Generated with Claude Code

…es a screen

A sheet whose nearest react-native-screens screen is a bottom-tab screen
stayed presented when the stack enclosing the tabs pushed a screen, so the
pushed screen ended up behind the sheet.

iOS: the presenter was only compared against the top of its nearest
navigation controller, which for a tab is the tabs container where the
active tab is always on top. Also walk the enclosing navigation controllers.

Android: react-native-screens dispatches lifecycle events for stack screens
only, so a tab screen never receives them. Also listen on the enclosing
screens, and let stacked sheets inherit them from their parent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 8, 2026

Copy link
Copy Markdown

@IjzerenHein is attempting to deploy a commit to the lodev09 Team on Vercel.

A member of the Team first needs to authorize it.

IjzerenHein and others added 4 commits October 8, 2026 08:57
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Listening to the enclosing screens meant any of them reappearing restored the
sheet. A sheet whose own screen was still covered by a push inside a nested
stack came back on top of that screen after the enclosing stack pushed and
popped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@lodev09 lodev09 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @IjzerenHein! Verified on the iOS 27 simulator and an Android emulator, A/B against main, including tabs as the root stack's initial route (the repro pushes them):

  • Sheet in a tab, and in a stack inside a tab, with the enclosing stack pushing and popping: hidden, then restored (on main it stays over the pushed screen)
  • Nested stack, inner push then outer push and pop: stays hidden, comes back with its own screen
  • Stacked sheets in a tab (Android): both hide and come back
  • Back swipe cancelled, then committed (iOS): stays hidden, restored on commit

Merged main and resolved the changelog conflict.

@vercel

vercel Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
react-native-true-sheet Ready Ready Preview Oct 13, 2026 4:02pm UTC

Request Review

@lodev09
lodev09 merged commit 7f70e98 into lodev09:main Oct 13, 2026
6 checks passed
@IjzerenHein

Copy link
Copy Markdown
Contributor Author

Awesome, thanks for merging @lodev09 ❤️
Do you know when the next release is due?

This branch was successfully deployed

1 active deployment
Preview — 3233ec90 Deployed Oct 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants