From 54df8308f0084c7252a0bb678ba0a6dfae83eca2 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Mon, 27 Apr 2026 17:05:50 +0200 Subject: [PATCH 1/3] refactor(tabs): rename TabsHost navState prop to navStateRequest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns the public API with RFC-1028's request/state distinction. The prop carries a *request* for a state change (with `baseProvenance` identifying the JS-side base it was derived from); the resulting state arrives back via `OnTabSelected` (with `provenance`). Naming both fields `provenance` was confusing — they refer to different generations. - Public TS: `TabsHostNavState` → `TabsHostNavStateRequest`, field `provenance` → `baseProvenance`, prop `navState` → `navStateRequest`. - Codegen specs (Android + iOS) updated to match; the C++ Props struct regenerates automatically. - Native binding boundary: `setNavStateRequest` override on Android ViewManager, prop access on iOS `updateProps`. - Internal native state types (`TabsNavState` Kotlin, `RNSTabsNavigationState` ObjC) kept — they model state, not requests. Only members that hold the most recent JS request are renamed (`jsNavState{,Request}`, `updateJSNavState{,Request}`, `_jsNavState{,Request}`). `TabSelectOp` payload stays `navState` since the op models a state update at the container level. - Apps consumer (`apps/...`) updated in lockstep so the workspace stays green. No behavior change. --- .../rnscreens/gamma/tabs/host/TabsHost.kt | 8 +++--- .../gamma/tabs/host/TabsHostViewManager.kt | 10 +++---- .../gamma/containers/tabs/TabsContainer.tsx | 16 +++++------ .../containers/tabs/TabsContainer.types.tsx | 4 +-- ios/tabs/host/RNSTabsHostComponentView.h | 2 +- ios/tabs/host/RNSTabsHostComponentView.mm | 17 +++++------ src/components/tabs/host/TabsHost.android.tsx | 4 +-- src/components/tabs/host/TabsHost.ios.tsx | 4 +-- src/components/tabs/host/TabsHost.types.ts | 28 ++++++++++--------- src/components/tabs/index.ts | 2 +- .../tabs/TabsHostAndroidNativeComponent.ts | 6 ++-- src/fabric/tabs/TabsHostIOSNativeComponent.ts | 6 ++-- 12 files changed, 55 insertions(+), 52 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt index c7f3dc545f..78629fd824 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt @@ -29,7 +29,7 @@ class TabsHost( TabsContainerDelegate, UIManagerListener { private val renderedScreens: ArrayList = arrayListOf() - private var jsNavState: TabsNavState = TabsNavState.EMPTY + private var jsNavStateRequest: TabsNavState = TabsNavState.EMPTY private val container: TabsContainer = TabsContainer(reactContext, this).apply { @@ -109,9 +109,9 @@ class TabsHost( container.removeAllTabsScreens() } - internal fun updateJSNavState(navState: TabsNavState) { - jsNavState = navState - container.setContainerOperation(TabSelectOp(jsNavState.copy())) + internal fun updateJSNavStateRequest(navStateRequest: TabsNavState) { + jsNavStateRequest = navStateRequest + container.setContainerOperation(TabSelectOp(jsNavStateRequest.copy())) } private val layoutCallback = diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt index 5b81f7ee8c..d4a336e4a8 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt @@ -72,14 +72,14 @@ class TabsHostViewManager : view.onViewManagerAddEventEmitters() } - override fun setNavState( + override fun setNavStateRequest( view: TabsHost, value: ReadableMap?, ) { - val navStateMap = requireNotNull(value) { "[RNScreens] NavState must not be nullish" } - val selectedScreenKey = requireNotNull(navStateMap.getString("selectedScreenKey")) - val provenance = requireNotNull(navStateMap.getInt("provenance")) - view.updateJSNavState(TabsNavState(selectedScreenKey, provenance)) + val navStateRequestMap = requireNotNull(value) { "[RNScreens] navStateRequest must not be nullish" } + val selectedScreenKey = requireNotNull(navStateRequestMap.getString("selectedScreenKey")) + val baseProvenance = requireNotNull(navStateRequestMap.getInt("baseProvenance")) + view.updateJSNavStateRequest(TabsNavState(selectedScreenKey, baseProvenance)) } override fun setRejectStaleNavStateUpdates( diff --git a/apps/src/shared/gamma/containers/tabs/TabsContainer.tsx b/apps/src/shared/gamma/containers/tabs/TabsContainer.tsx index bb3910c7fe..516252b6ed 100644 --- a/apps/src/shared/gamma/containers/tabs/TabsContainer.tsx +++ b/apps/src/shared/gamma/containers/tabs/TabsContainer.tsx @@ -3,7 +3,7 @@ import { I18nManager, type NativeSyntheticEvent } from 'react-native'; import { type TabSelectedEvent, Tabs, - type TabsHostNavState, + type TabsHostNavStateRequest, } from 'react-native-screens'; import type { SelectTabMethod, @@ -43,7 +43,7 @@ export function TabsContainer(props: TabsContainerProps) { determineInitialTabsContainerState, ); - const hostNavState = useTabsHostNavState(tabsNavState); + const hostNavStateRequest = useTabsHostNavStateRequest(tabsNavState); const onTabSelectedCallback = React.useCallback( (event: NativeSyntheticEvent) => { @@ -74,7 +74,7 @@ export function TabsContainer(props: TabsContainerProps) { return ( @@ -90,17 +90,17 @@ export function TabsContainer(props: TabsContainerProps) { ); } -function useTabsHostNavState( +function useTabsHostNavStateRequest( tabsNavState: TabsContainerState, -): TabsHostNavState { - const hostNavState: TabsHostNavState = React.useMemo(() => { +): TabsHostNavStateRequest { + const hostNavStateRequest: TabsHostNavStateRequest = React.useMemo(() => { return { selectedScreenKey: tabsNavState.suggestedState.selectedRouteKey, - provenance: tabsNavState.suggestedState.provenance, + baseProvenance: tabsNavState.suggestedState.provenance, }; }, [tabsNavState.suggestedState]); - return hostNavState; + return hostNavStateRequest; } function useSanitizeRouteConfigs(routeConfigs: TabRouteConfig[]) { diff --git a/apps/src/shared/gamma/containers/tabs/TabsContainer.types.tsx b/apps/src/shared/gamma/containers/tabs/TabsContainer.types.tsx index e2f6f22b73..80e5023192 100644 --- a/apps/src/shared/gamma/containers/tabs/TabsContainer.types.tsx +++ b/apps/src/shared/gamma/containers/tabs/TabsContainer.types.tsx @@ -80,12 +80,12 @@ export type TabsNavigationAction = export type TabsHostConfig = Omit< TabsHostProps, | 'children' - | 'navState' + | 'navStateRequest' >; export type TabsContainerProps = Omit< TabsHostProps, - 'children' | 'navState' + 'children' | 'navStateRequest' > & { routeConfigs: TabRouteConfig[]; /** diff --git a/ios/tabs/host/RNSTabsHostComponentView.h b/ios/tabs/host/RNSTabsHostComponentView.h index ebd8a2a7db..b3991c0fca 100644 --- a/ios/tabs/host/RNSTabsHostComponentView.h +++ b/ios/tabs/host/RNSTabsHostComponentView.h @@ -50,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Last navigation state requested by JS. Will be nonnull after first prop update. */ -@property (nonatomic, strong, readonly, nullable) RNSTabsNavigationState *navState; +@property (nonatomic, strong, readonly, nullable) RNSTabsNavigationState *navStateRequest; @property (nonatomic, readonly) BOOL rejectStaleNavStateUpdates; diff --git a/ios/tabs/host/RNSTabsHostComponentView.mm b/ios/tabs/host/RNSTabsHostComponentView.mm index 77431c5f2b..7e3f644043 100644 --- a/ios/tabs/host/RNSTabsHostComponentView.mm +++ b/ios/tabs/host/RNSTabsHostComponentView.mm @@ -53,7 +53,7 @@ @implementation RNSTabsHostComponentView { BOOL _hasModifiedBottomAccessoryInCurrentTransation; BOOL _needsTabBarAppearanceUpdate; - RNSTabsNavigationState *_Nullable _jsNavState; + RNSTabsNavigationState *_Nullable _jsNavStateRequest; } - (instancetype)initWithFrame:(CGRect)frame @@ -268,14 +268,15 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props const auto &oldComponentProps = *std::static_pointer_cast(_props); const auto &newComponentProps = *std::static_pointer_cast(props); - if (newComponentProps.navState.selectedScreenKey != oldComponentProps.navState.selectedScreenKey || - newComponentProps.navState.provenance != oldComponentProps.navState.provenance) { - NSString *selectedScreenKey = RCTNSStringFromStringNilIfEmpty(newComponentProps.navState.selectedScreenKey); + if (newComponentProps.navStateRequest.selectedScreenKey != oldComponentProps.navStateRequest.selectedScreenKey || + newComponentProps.navStateRequest.baseProvenance != oldComponentProps.navStateRequest.baseProvenance) { + NSString *selectedScreenKey = RCTNSStringFromStringNilIfEmpty(newComponentProps.navStateRequest.selectedScreenKey); RCTAssert(selectedScreenKey != nil, @"[RNScreens] selectedScreenKey MUST NOT be nil"); - RCTAssert(newComponentProps.navState.provenance >= 0, @"[RNScreens] provenance MUST BE >= 0]"); - _jsNavState = [RNSTabsNavigationState stateWithSelectedScreenKey:selectedScreenKey - provenance:newComponentProps.navState.provenance]; - [_controller setPendingNavigationStateUpdate:[_jsNavState cloneState]]; + RCTAssert(newComponentProps.navStateRequest.baseProvenance >= 0, @"[RNScreens] baseProvenance MUST BE >= 0]"); + _jsNavStateRequest = + [RNSTabsNavigationState stateWithSelectedScreenKey:selectedScreenKey + provenance:newComponentProps.navStateRequest.baseProvenance]; + [_controller setPendingNavigationStateUpdate:[_jsNavStateRequest cloneState]]; } if (newComponentProps.rejectStaleNavStateUpdates != oldComponentProps.rejectStaleNavStateUpdates) { diff --git a/src/components/tabs/host/TabsHost.android.tsx b/src/components/tabs/host/TabsHost.android.tsx index d0e9712d55..110998379e 100644 --- a/src/components/tabs/host/TabsHost.android.tsx +++ b/src/components/tabs/host/TabsHost.android.tsx @@ -25,7 +25,7 @@ function TabsHost(props: TabsHostProps) { direction, nativeContainerStyle, onTabSelected, - navState, + navStateRequest, ...filteredBaseProps } = baseProps; @@ -41,7 +41,7 @@ function TabsHost(props: TabsHostProps) { return ( ; // Events diff --git a/src/fabric/tabs/TabsHostIOSNativeComponent.ts b/src/fabric/tabs/TabsHostIOSNativeComponent.ts index 71daa839c7..f0cc4635cc 100644 --- a/src/fabric/tabs/TabsHostIOSNativeComponent.ts +++ b/src/fabric/tabs/TabsHostIOSNativeComponent.ts @@ -13,9 +13,9 @@ type TabSelectedEvent = Readonly<{ isNativeAction: boolean; }>; -type NavigationState = Readonly<{ +type NavigationStateRequest = Readonly<{ selectedScreenKey: string; - provenance: CT.Int32; + baseProvenance: CT.Int32; }>; type TabSelectionRejectedEvent = Readonly<{ @@ -57,7 +57,7 @@ type TabBarControllerMode = 'automatic' | 'tabBar' | 'tabSidebar'; export interface NativeProps extends ViewProps { // Control - navState: NavigationState; + navStateRequest: NavigationStateRequest; rejectStaleNavStateUpdates?: CT.WithDefault; // Events From e0b94820ca37a056588162e0be7175c4df2b103d Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Tue, 28 Apr 2026 18:26:37 +0200 Subject: [PATCH 2/3] refactor(tabs): rename _jsNavStateRequest ivar to _navStateRequest Aligns the iOS ivar name with the renamed `navStateRequest` prop on RNSTabsHost. The `_js` prefix referenced the prop's prior name and is no longer meaningful. --- ios/tabs/host/RNSTabsHostComponentView.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/tabs/host/RNSTabsHostComponentView.mm b/ios/tabs/host/RNSTabsHostComponentView.mm index 7e3f644043..19a7281b1a 100644 --- a/ios/tabs/host/RNSTabsHostComponentView.mm +++ b/ios/tabs/host/RNSTabsHostComponentView.mm @@ -53,7 +53,7 @@ @implementation RNSTabsHostComponentView { BOOL _hasModifiedBottomAccessoryInCurrentTransation; BOOL _needsTabBarAppearanceUpdate; - RNSTabsNavigationState *_Nullable _jsNavStateRequest; + RNSTabsNavigationState *_Nullable _navStateRequest; } - (instancetype)initWithFrame:(CGRect)frame @@ -273,10 +273,10 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props NSString *selectedScreenKey = RCTNSStringFromStringNilIfEmpty(newComponentProps.navStateRequest.selectedScreenKey); RCTAssert(selectedScreenKey != nil, @"[RNScreens] selectedScreenKey MUST NOT be nil"); RCTAssert(newComponentProps.navStateRequest.baseProvenance >= 0, @"[RNScreens] baseProvenance MUST BE >= 0]"); - _jsNavStateRequest = + _navStateRequest = [RNSTabsNavigationState stateWithSelectedScreenKey:selectedScreenKey provenance:newComponentProps.navStateRequest.baseProvenance]; - [_controller setPendingNavigationStateUpdate:[_jsNavStateRequest cloneState]]; + [_controller setPendingNavigationStateUpdate:[_navStateRequest cloneState]]; } if (newComponentProps.rejectStaleNavStateUpdates != oldComponentProps.rejectStaleNavStateUpdates) { From c4098e10b89a0b773652806e8c91183492d087bc Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Tue, 28 Apr 2026 18:27:38 +0200 Subject: [PATCH 3/3] Typo --- ios/tabs/host/RNSTabsHostComponentView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/tabs/host/RNSTabsHostComponentView.mm b/ios/tabs/host/RNSTabsHostComponentView.mm index 19a7281b1a..6cbec6f17d 100644 --- a/ios/tabs/host/RNSTabsHostComponentView.mm +++ b/ios/tabs/host/RNSTabsHostComponentView.mm @@ -272,7 +272,7 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props newComponentProps.navStateRequest.baseProvenance != oldComponentProps.navStateRequest.baseProvenance) { NSString *selectedScreenKey = RCTNSStringFromStringNilIfEmpty(newComponentProps.navStateRequest.selectedScreenKey); RCTAssert(selectedScreenKey != nil, @"[RNScreens] selectedScreenKey MUST NOT be nil"); - RCTAssert(newComponentProps.navStateRequest.baseProvenance >= 0, @"[RNScreens] baseProvenance MUST BE >= 0]"); + RCTAssert(newComponentProps.navStateRequest.baseProvenance >= 0, @"[RNScreens] baseProvenance MUST BE >= 0"); _navStateRequest = [RNSTabsNavigationState stateWithSelectedScreenKey:selectedScreenKey provenance:newComponentProps.navStateRequest.baseProvenance];