software-mansion / software-mansion/react-native-screens

iOS Release crash on tab select: nil selectedScreenKey reaches -[RNSTabsNavigationState cloneState] (stringWithString:nil)

Open
#4,141 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

missing-repro platform:ios
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 screenKeyForSelectedViewControllerscreenKeyForViewController: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
  1. In progressNavigationState:withOrigin:, early-return (or keep current state) when newSelectedScreenKey == nil — a Release-mode mirror of the existing assert; UIKit state is reconciled on the next container update.
  2. 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
  1. 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's hidden prop flips after async state rehydration)
  2. Release build (so RCTAssert is compiled out), iOS 26 device
  3. 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.