Describe the bug
On iOS, TabAppearModifier force-selects any tab whose index >= 4 in onAppear:
#if os(iOS)
if context.index >= 4, context.props.selectedPage != context.tabData.key {
context.onSelect(context.tabData.key)
}
#endif
This is intended to sync selection for tabs nested under iOS's "More" overflow tab, which only exists when there are more than 5 visible tabs. With exactly 5 tabs there is no More tab, but the 5th tab (index 4) still matches index >= 4. So whenever that tab's scene re-appears — e.g. when the tab bar is re-shown after a screen hid it via .hideTabBar (true → false) — it fires onSelect and steals the selection, producing a phantom tab switch.
It cannot be corrected from JS: selectedPage is a value-diffed controlled prop and there's no imperative setter, so once the native bar drifts, JS re-passing the same selectedPage is a no-op.
To Reproduce
With a 5-tab native bottom tabs setup (here via @bottom-tabs/react-navigation + expo-router):
- Select the 5th tab (index 4) once.
- Navigate to a nested screen that sets
tabBarHidden = true.
- Press back (tab bar re-shows,
tabBarHidden = false).
➡️ The app jumps to the 5th tab instead of staying on the tab you returned to.
Root cause
On iOS the only code paths that emit onPageSelected are (a) a real user tab tap (onTabItemEvent) and (b) this index >= 4 appear-handler. In the repro there is no tap, so the switch comes from (b): the 5th tab's onAppear fires during the tab-bar re-materialization and force-selects itself.
Fix
Gate the force-select on actual overflow (more than 5 visible tabs, i.e. when a real "More" tab exists):
#if os(iOS)
- if context.index >= 4, context.props.selectedPage != context.tabData.key {
+ if context.props.filteredItems.count > 5, context.index >= 4, context.props.selectedPage != context.tabData.key {
context.onSelect(context.tabData.key)
}
#endif
This preserves the existing behavior for >5-tab (More menu) configurations and removes the spurious selection for ≤5-tab bars.
Versions
Reproduced on react-native-bottom-tabs 1.1.0 and 1.2.0 (iOS).
Describe the bug
On iOS,
TabAppearModifierforce-selects any tab whoseindex >= 4inonAppear:This is intended to sync selection for tabs nested under iOS's "More" overflow tab, which only exists when there are more than 5 visible tabs. With exactly 5 tabs there is no More tab, but the 5th tab (index 4) still matches
index >= 4. So whenever that tab's scene re-appears — e.g. when the tab bar is re-shown after a screen hid it via.hideTabBar(true→false) — it firesonSelectand steals the selection, producing a phantom tab switch.It cannot be corrected from JS:
selectedPageis a value-diffed controlled prop and there's no imperative setter, so once the native bar drifts, JS re-passing the sameselectedPageis a no-op.To Reproduce
With a 5-tab native bottom tabs setup (here via
@bottom-tabs/react-navigation+ expo-router):tabBarHidden = true.tabBarHidden = false).➡️ The app jumps to the 5th tab instead of staying on the tab you returned to.
Root cause
On iOS the only code paths that emit
onPageSelectedare (a) a real user tab tap (onTabItemEvent) and (b) thisindex >= 4appear-handler. In the repro there is no tap, so the switch comes from (b): the 5th tab'sonAppearfires during the tab-bar re-materialization and force-selects itself.Fix
Gate the force-select on actual overflow (more than 5 visible tabs, i.e. when a real "More" tab exists):
This preserves the existing behavior for >5-tab (More menu) configurations and removes the spurious selection for ≤5-tab bars.
Versions
Reproduced on
react-native-bottom-tabs1.1.0 and 1.2.0 (iOS).