Skip to content

TabAppearModifier hijacks the 5th tab (index 4) selection when a hidden tab bar re-appears #525

@davidecallegaro

Description

@davidecallegaro

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 (truefalse) — 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):

  1. Select the 5th tab (index 4) once.
  2. Navigate to a nested screen that sets tabBarHidden = true.
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions