Skip to content

Commit 6b2be87

Browse files
kkafarclaude
andauthored
refactor(tabs): rename onTabChange event to onTabSelected (#3789)
## Description Renames the `onTabChange` event prop to `onTabSelected` across the entire tabs stack, and aligns the imperative tab-switching API to use "select" nomenclature as well. The previous name was ambiguous — "change" describes the act of switching, while "selected" more precisely communicates the semantics: a tab was chosen/selected by the user or programmatically. ## Changes - **Codegen specs** (`TabsHostIOSNativeComponent`, `TabsHostAndroidNativeComponent`): `onTabChange` → `onTabSelected`, `TabChangeEvent` → `TabSelectedEvent` - **TypeScript public API** (`TabsHost.types`, `useTabsHost`, `TabsHost.ios`, `TabsHost.android`): prop, callback variable, and type renamed accordingly - **Android**: `TabsHostTabChangeEvent` → `TabsHostTabSelectedEvent` (file renamed), native event strings `topTabChange` → `topTabSelected` / `onTabChange` → `onTabSelected`, `TabChangeOp` → `TabSelectOp`, `emitOnTabChangeEvent` → `emitOnTabSelectedEvent` - **iOS**: `OnTabChangePayload` → `OnTabSelectedPayload`, `emitOnTabChange:` → `emitOnTabSelected:`, `shouldPreventNativeTabChange` → `shouldPreventNativeTabSelection` - **Example app — imperative API & types** (`TabsContainer`, `TabsContainer.types`, `TabsNavigationContext`, reducer, index, test files): - `ChangeTabMethod` → `SelectTabMethod`, `changeTabTo` → `selectTab`, `tabChangeActionMethod` → `selectTabMethod` - `TabsNavigationActionChangeTab` → `TabsNavigationActionSelectTab`, `TabsNavigationActionNativeChangeTab` → `TabsNavigationActionNativeSelectTab` - Action type literals `'tab-change'` → `'tab-select'`, `'native-tab-change'` → `'native-tab-select'` - Reducer handlers `tabsActionChangeTabHandler` → `tabsActionSelectTabHandler`, `tabsActionNativeChangeTabHandler` → `tabsActionNativeSelectTabHandler` - Updated usages in test files (`test-tabs-more-navigation-controller`, `test-tabs-simple-nav`) ## Test plan Trigger tab selection in the example app and verify the `onTabSelected` callback fires correctly on both iOS and Android. ## Checklist - [ ] Included code example that can be used to test this change. - [ ] Updated / created local changelog entries in relevant test files. - [ ] For visual changes, included screenshots / GIFs / recordings documenting the change. - [x] For API changes, updated relevant public types. - [ ] Ensured that CI passes --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]>
1 parent 9a4fbaa commit 6b2be87

24 files changed

Lines changed: 109 additions & 115 deletions

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,12 @@ internal class TabsContainer(
253253
}
254254

255255
check(hasPendingOperation) { "[RNScreens] Attempt to update container with empty state and no pending update" }
256-
check(pendingOperation is TabChangeOp)
257-
val tabChangeOp = pendingOperation as TabChangeOp
256+
check(pendingOperation is TabSelectOp)
257+
val tabSelectOp = pendingOperation as TabSelectOp
258258

259259
val nextSelectedMenuItemId =
260-
checkNotNull(getMenuItemIdForFragment(requireFragmentForScreenKey(tabChangeOp.navState.selectedKey))) {
261-
"[RNScreens] Failed to find Menu Item for screenKey: ${tabChangeOp.navState.selectedKey}"
260+
checkNotNull(getMenuItemIdForFragment(requireFragmentForScreenKey(tabSelectOp.navState.selectedKey))) {
261+
"[RNScreens] Failed to find Menu Item for screenKey: ${tabSelectOp.navState.selectedKey}"
262262
}
263263

264264
if (bottomNavigationView.selectedItemId != nextSelectedMenuItemId || navState.isEmpty()) {
@@ -373,8 +373,7 @@ internal class TabsContainer(
373373
appearanceCoordinator.updateTabAppearance(themedContext, this)
374374
}
375375

376-
private fun getFragmentForMenuItemId(itemId: Int): TabsScreenFragment? =
377-
tabsModel.getOrNull(fragmentIndexForMenuItemId(itemId))
376+
private fun getFragmentForMenuItemId(itemId: Int): TabsScreenFragment? = tabsModel.getOrNull(fragmentIndexForMenuItemId(itemId))
378377

379378
private fun getMenuItemIdForFragment(tabsScreenFragment: TabsScreenFragment): Int? =
380379
tabsModel.indexOfFirst { it === tabsScreenFragment }.takeIf { it != -1 }?.let {
@@ -396,11 +395,9 @@ internal class TabsContainer(
396395

397396
override fun getResolvedUiNightMode() = colorSchemeCoordinator.getResolvedUiNightMode()
398397

399-
override fun addColorSchemeListener(listener: ColorSchemeListener) =
400-
colorSchemeCoordinator.addColorSchemeListener(listener)
398+
override fun addColorSchemeListener(listener: ColorSchemeListener) = colorSchemeCoordinator.addColorSchemeListener(listener)
401399

402-
override fun removeColorSchemeListener(listener: ColorSchemeListener) =
403-
colorSchemeCoordinator.removeColorSchemeListener(listener)
400+
override fun removeColorSchemeListener(listener: ColorSchemeListener) = colorSchemeCoordinator.removeColorSchemeListener(listener)
404401

405402
override fun onAppearanceChanged(tabsScreen: TabsScreen) {
406403
if (selectedTab.tabsScreen === tabsScreen) {
@@ -427,11 +424,10 @@ internal class TabsContainer(
427424
override fun getFragmentForTabsScreen(tabsScreen: TabsScreen): TabsScreenFragment? =
428425
tabsModel.find {
429426
it.tabsScreen ===
430-
tabsScreen
427+
tabsScreen
431428
}
432429

433-
private fun getFragmentForScreenKey(screenKey: String): TabsScreenFragment? =
434-
tabsModel.find { it.requireScreenKey == screenKey }
430+
private fun getFragmentForScreenKey(screenKey: String): TabsScreenFragment? = tabsModel.find { it.requireScreenKey == screenKey }
435431

436432
private fun requireFragmentForScreenKey(screenKey: String): TabsScreenFragment =
437433
checkNotNull(getFragmentForScreenKey(screenKey)) {
@@ -490,8 +486,7 @@ internal class TabsContainer(
490486
}
491487
}
492488

493-
override fun getInterfaceInsets(): EdgeInsets =
494-
EdgeInsets(0.0f, 0.0f, 0.0f, bottomNavigationView.height.toFloat())
489+
override fun getInterfaceInsets(): EdgeInsets = EdgeInsets(0.0f, 0.0f, 0.0f, bottomNavigationView.height.toFloat())
495490

496491
private fun getInsetsForBottomNavigationView(insets: WindowInsets): WindowInsets? {
497492
if (tabBarRespectsIMEInsets) {
@@ -528,8 +523,7 @@ internal class TabsContainerInvalidationFlags(
528523
var isNavigationMenuAppearanceInvalidated: Boolean = false,
529524
var isNavigationMenuStructureInvalidated: Boolean = false,
530525
) {
531-
internal fun any(): Boolean =
532-
isSelectedTabInvalidated || isNavigationMenuAppearanceInvalidated || isNavigationMenuStructureInvalidated
526+
internal fun any(): Boolean = isSelectedTabInvalidated || isNavigationMenuAppearanceInvalidated || isNavigationMenuStructureInvalidated
533527

534528
internal fun invalidateAll() {
535529
isSelectedTabInvalidated = true

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainerOps.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ package com.swmansion.rnscreens.gamma.tabs.container
22

33
internal sealed class TabsContainerOp
44

5-
internal data class TabChangeOp(
5+
internal data class TabSelectOp(
66
val navState: TabsNavState,
77
) : TabsContainerOp()

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import com.facebook.react.uimanager.ThemedReactContext
1212
import com.facebook.react.uimanager.UIManagerHelper
1313
import com.swmansion.rnscreens.gamma.common.colorscheme.ColorScheme
1414
import com.swmansion.rnscreens.gamma.helpers.getFabricUIManagerNotNull
15-
import com.swmansion.rnscreens.gamma.tabs.container.TabChangeOp
15+
import com.swmansion.rnscreens.gamma.tabs.container.TabSelectOp
1616
import com.swmansion.rnscreens.gamma.tabs.container.TabsContainer
1717
import com.swmansion.rnscreens.gamma.tabs.container.TabsContainerDelegate
1818
import com.swmansion.rnscreens.gamma.tabs.container.TabsNavState
@@ -108,7 +108,7 @@ class TabsHost(
108108

109109
internal fun updateJSNavState(navState: TabsNavState) {
110110
jsNavState = navState
111-
container.setContainerOperation(TabChangeOp(jsNavState.copy()))
111+
container.setContainerOperation(TabSelectOp(jsNavState.copy()))
112112
}
113113

114114
private val layoutCallback =
@@ -158,7 +158,7 @@ class TabsHost(
158158
hasTriggeredSpecialEffect: Boolean,
159159
isNativeAction: Boolean,
160160
) {
161-
eventEmitter.emitOnTabChangeEvent(
161+
eventEmitter.emitOnTabSelectedEvent(
162162
navState.selectedKey,
163163
navState.provenance,
164164
isRepeated,

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostEventEmitter.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@ package com.swmansion.rnscreens.gamma.tabs.host
22

33
import com.facebook.react.bridge.ReactContext
44
import com.swmansion.rnscreens.gamma.common.event.BaseEventEmitter
5-
import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabChangeEvent
5+
import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabSelectedEvent
66

77
internal class TabsHostEventEmitter(
88
reactContext: ReactContext,
99
viewTag: Int,
1010
) : BaseEventEmitter(reactContext, viewTag) {
11-
fun emitOnTabChangeEvent(
11+
fun emitOnTabSelectedEvent(
1212
selectedScreenKey: String,
1313
provenance: Int,
1414
isRepeated: Boolean,
1515
hasTriggeredSpecialEffect: Boolean,
1616
isNativeAction: Boolean,
1717
) {
1818
reactEventDispatcher.dispatchEvent(
19-
TabsHostTabChangeEvent(
19+
TabsHostTabSelectedEvent(
2020
surfaceId,
2121
viewTag,
2222
selectedScreenKey,

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import com.facebook.react.viewmanagers.RNSTabsHostAndroidManagerInterface
1212
import com.swmansion.rnscreens.gamma.common.colorscheme.ColorScheme
1313
import com.swmansion.rnscreens.gamma.helpers.makeEventRegistrationInfo
1414
import com.swmansion.rnscreens.gamma.tabs.container.TabsNavState
15-
import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabChangeEvent
15+
import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabSelectedEvent
1616
import com.swmansion.rnscreens.gamma.tabs.screen.TabsScreen
1717

1818
@ReactModule(name = TabsHostViewManager.REACT_CLASS)
@@ -57,7 +57,7 @@ class TabsHostViewManager :
5757

5858
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
5959
mutableMapOf(
60-
makeEventRegistrationInfo(TabsHostTabChangeEvent),
60+
makeEventRegistrationInfo(TabsHostTabSelectedEvent),
6161
)
6262

6363
override fun addEventEmitters(

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/event/TabsHostTabChangeEvent.kt renamed to android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/event/TabsHostTabSelectedEvent.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import com.facebook.react.bridge.WritableMap
55
import com.facebook.react.uimanager.events.Event
66
import com.swmansion.rnscreens.gamma.common.event.NamingAwareEventType
77

8-
class TabsHostTabChangeEvent(
8+
class TabsHostTabSelectedEvent(
99
surfaceId: Int,
1010
viewId: Int,
1111
val selectedScreenKey: String,
1212
val provenance: Int,
1313
val isRepeated: Boolean,
1414
val hasTriggeredSpecialEffect: Boolean,
1515
val isNativeAction: Boolean,
16-
) : Event<TabsHostTabChangeEvent>(surfaceId, viewId),
16+
) : Event<TabsHostTabSelectedEvent>(surfaceId, viewId),
1717
NamingAwareEventType {
1818
override fun getEventName() = EVENT_NAME
1919

@@ -32,8 +32,8 @@ class TabsHostTabChangeEvent(
3232
}
3333

3434
companion object : NamingAwareEventType {
35-
const val EVENT_NAME = "topTabChange"
36-
const val EVENT_REGISTRATION_NAME = "onTabChange"
35+
const val EVENT_NAME = "topTabSelected"
36+
const val EVENT_REGISTRATION_NAME = "onTabSelected"
3737

3838
private const val EK_SELECTED_KEY = "selectedScreenKey"
3939
private const val EK_PROVENANCE = "provenance"

apps/src/shared/gamma/containers/tabs/TabsContainer.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import React from 'react';
22
import { I18nManager, Platform, type NativeSyntheticEvent } from 'react-native';
33
import {
44
SCREEN_KEY_MORE_NAV_CTRL,
5-
type TabChangeEvent,
5+
type TabSelectedEvent,
66
Tabs,
77
type TabsHostNavState,
88
} from 'react-native-screens';
99
import { SafeAreaView, type SafeAreaViewProps } from 'react-native-screens/experimental'
1010
import type {
11-
ChangeTabMethod,
11+
SelectTabMethod,
1212
TabRoute,
1313
TabRouteConfig,
1414
TabRouteOptions,
@@ -33,7 +33,7 @@ export function TabsContainer(props: TabsContainerProps) {
3333
routeConfigs,
3434
initialFocusedName,
3535
experimentalControlNavigationStateInJS,
36-
onTabChange,
36+
onTabSelected,
3737
...restProps
3838
} = props;
3939

@@ -57,14 +57,14 @@ export function TabsContainer(props: TabsContainerProps) {
5757

5858
const hostNavState = useTabsHostNavState(tabsNavState);
5959

60-
const onTabChangeCallback = React.useCallback(
61-
(event: NativeSyntheticEvent<TabChangeEvent>) => {
60+
const onTabSelectedCallback = React.useCallback(
61+
(event: NativeSyntheticEvent<TabSelectedEvent>) => {
6262
// First call user provided callback
63-
onTabChange?.(event);
63+
onTabSelected?.(event);
6464

6565
// Perform our logic
6666
const screenKey = event.nativeEvent.selectedScreenKey;
67-
console.log(`[Tabs] onTabChangeCallback: ${screenKey}`);
67+
console.log(`[Tabs] onTabSelectedCallback: ${screenKey}`);
6868

6969
// Please note that the `useTransition` hook can not be used here,
7070
// because it intruduces additional renders, which lead
@@ -73,18 +73,18 @@ export function TabsContainer(props: TabsContainerProps) {
7373
React.startTransition(() => {
7474
RNSLog.info(`Starting transition to ${screenKey}`);
7575
dispatch({
76-
type: 'native-tab-change',
76+
type: 'native-tab-select',
7777
routeKey: screenKey,
7878
nativeEvent: event.nativeEvent,
7979
});
8080
});
8181
},
82-
[onTabChange],
82+
[onTabSelected],
8383
);
8484

85-
const tabChangeActionMethod: ChangeTabMethod = React.useCallback(
85+
const selectTabMethod: SelectTabMethod = React.useCallback(
8686
(routeKey: string) => {
87-
dispatch({ type: 'tab-change', routeKey });
87+
dispatch({ type: 'tab-select', routeKey });
8888
},
8989
[],
9090
);
@@ -93,7 +93,7 @@ export function TabsContainer(props: TabsContainerProps) {
9393
<Tabs.Host
9494
// Use controlled tabs by default, but allow to overwrite if user wants to
9595
navState={hostNavState}
96-
onTabChange={onTabChangeCallback}
96+
onTabSelected={onTabSelectedCallback}
9797
experimentalControlNavigationStateInJS={
9898
experimentalControlNavigationStateInJS
9999
}
@@ -114,7 +114,7 @@ export function TabsContainer(props: TabsContainerProps) {
114114
routeKey: route.routeKey,
115115
routeOptions: { ...route.options },
116116
setRouteOptions,
117-
changeTabTo: tabChangeActionMethod,
117+
selectTab: selectTabMethod,
118118
isSelected: isSelected,
119119
shouldRenderContents: isSelected || pendingForUpdate,
120120
};

apps/src/shared/gamma/containers/tabs/TabsContainer.types.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import type {
3-
TabChangeEvent,
3+
TabSelectedEvent,
44
TabsHostProps,
55
TabsScreenProps,
66
} from 'react-native-screens';
@@ -52,15 +52,15 @@ export type TabsContainerState = {
5252

5353
/// Navigation actions
5454

55-
export type TabsNavigationActionChangeTab = {
56-
type: 'tab-change';
55+
export type TabsNavigationActionSelectTab = {
56+
type: 'tab-select';
5757
routeKey: string;
5858
};
5959

60-
export type TabsNavigationActionNativeChangeTab = {
61-
type: 'native-tab-change';
60+
export type TabsNavigationActionNativeSelectTab = {
61+
type: 'native-tab-select';
6262
routeKey: string;
63-
nativeEvent: TabChangeEvent;
63+
nativeEvent: TabSelectedEvent;
6464
};
6565

6666
export type TabsNavigationActionSetOptions = {
@@ -70,9 +70,9 @@ export type TabsNavigationActionSetOptions = {
7070
};
7171

7272
export type TabsNavigationAction =
73-
| TabsNavigationActionChangeTab
73+
| TabsNavigationActionSelectTab
7474
| TabsNavigationActionSetOptions
75-
| TabsNavigationActionNativeChangeTab;
75+
| TabsNavigationActionNativeSelectTab;
7676

7777
/// TabsContainer props
7878

@@ -103,4 +103,4 @@ export type SetTabOptionsMethod = (
103103
options: Partial<TabRouteOptions>,
104104
) => void;
105105

106-
export type ChangeTabMethod = (routeKey: string) => void;
106+
export type SelectTabMethod = (routeKey: string) => void;

apps/src/shared/gamma/containers/tabs/contexts/TabsNavigationContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import React from 'react';
22
import type {
33
TabRouteOptions,
44
SetTabOptionsMethod,
5-
ChangeTabMethod,
5+
SelectTabMethod,
66
} from '../TabsContainer.types';
77

88
export type TabsNavigationContextPayload = {
99
routeKey: string;
1010
routeOptions: TabRouteOptions;
1111
setRouteOptions: SetTabOptionsMethod;
12-
changeTabTo: ChangeTabMethod;
12+
selectTab: SelectTabMethod;
1313
isSelected: boolean;
1414
shouldRenderContents: boolean;
1515
};

apps/src/shared/gamma/containers/tabs/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export type {
66
TabsContainerProps,
77
TabsHostConfig,
88
TabsNavigationAction,
9-
TabsNavigationActionChangeTab,
9+
TabsNavigationActionSelectTab,
1010
TabsNavigationActionSetOptions,
1111
TabsContainerState,
1212
} from './TabsContainer.types';

0 commit comments

Comments
 (0)