software-mansion / software-mansion/react-native-screens
iOS Release crash on tab select: nil selectedScreenKey reaches -[RNSTabsNavigationState cloneState] (stringWithString:nil)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.7k
- Forks
- 714
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 71
Description
Description
In Release builds on iOS 26, selecting a bottom tab can crash the app with NSInvalidArgumentException inside -[RNSTabsNavigationState cloneState]:
2 Foundation -[NSPlaceholderString initWithString:] + 940 (NSString.m:2327)
3 Foundation +[NSString stringWithString:] + 32 (NSString.m:213)
4 app -[RNSTabsNavigationState cloneState] + 76 (RNSTabsNavigationState.mm:17)
5 app -[RNSTabBarController progressNavigationState:withOrigin:] + 124 (RNSTabBarController.mm:648)
6 app -[RNSTabBarController updateNavigationStateOnModelUpdate] + 48 (RNSTabBarController.mm:315)
7 app -[RNSTabBarController userDidSelectViewController:] + 96 (RNSTabBarController.mm:355)
8 app -[RNSTabBarController tabBarController:didSelectViewController:] + 24 (RNSTabBarController.mm:448)
9 UIKitCore -[UITabBarController _setSelectedViewControllerAndNotify:] + 276
Root cause
On tab selection, updateNavigationStateOnModelUpdate derives the key via screenKeyForSelectedViewController → screenKeyForViewController: → getScreenKeyOrNull, which reads self.tabScreenComponentView.screenKey (RNSTabsScreenViewController.mm). That is nil while the selected tab's component view is detached — e.g. while tab triggers are being remounted. In our app, a persisted "show this tab" toggle rehydrates from storage shortly after cold launch and remounts the <NativeTabs.Trigger> list (expo-router); a tab tap landing in that window reliably produces the nil.
Every guard on this path is RCTAssert (progressNavigationState: line ~637, screenKeyForViewController: line ~672), which compiles out of Release builds. So the nil key is silently stored into _navigationState, and the app aborts on the next cloneState call at RNSTabsNavigationState.mm:17 ([NSString stringWithString:nil] throws). Debug builds would have tripped the assert instead, which is presumably why this survived testing.
Still present on main (ios/tabs/host/RNSTabsNavigationState.mm / RNSTabBarController.mm).
Suggested fix
- In
progressNavigationState:withOrigin:, early-return (or keep current state) whennewSelectedScreenKey == nil— a Release-mode mirror of the existing assert; UIKit state is reconciled on the next container update. - In
cloneState/cloneRequest, use[self.selectedScreenKey copy]instead of[NSString stringWithString:...]— equivalent for strings, nil-safe.
We're shipping exactly this as a patch-package patch and it resolves the crash. Happy to open a PR if useful.
Steps to reproduce
- iOS app using native bottom tabs (we hit it via expo-router
unstable-native-tabs) with tab triggers that remount shortly after launch (e.g. a trigger'shiddenprop flips after async state rehydration) - Release build (so
RCTAssertis compiled out), iOS 26 device - Cold launch, tap a bottom tab within the first seconds while triggers remount
Snack or a link to a repository
Crash observed in a production TestFlight app (full Apple crash log available on request — incident BA77F785-5D9D-4651-9FB0-BEFE15EFB0B6).
Screens version
4.25.2
React Native version
0.83.4
Platforms
iOS
Device
Real device
Device model
iPhone 15 Pro (iPhone16,1), iOS 26.5
Architecture
Fabric (New Architecture)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with ios/tabs/host/RNSTabBarController.mm at progressNavigationState:withOrigin: and RNSTabsNavigationState.mm at cloneState and cloneRequest. Reproduce the Release-build tab selection during trigger remounting, then verify that a nil selected screen key no longer reaches the crash path and that subsequent navigation state updates still work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, ios, react-native
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100