Skip to content

Commit 8c915dc

Browse files
authored
refactor(tabs): rename TabsHost navState prop to navStateRequest (#3943)
## Description Aligns the `TabsHost` public API with [RFC-1028](software-mansion/react-native-screens-labs#1174 request/state distinction. The prop currently named `navState` carries a *request* for a state change — not the state itself. The resulting state is delivered back to JS through the `OnTabSelected` event. Today both sides expose a field called `provenance`, but they refer to different things: - on the request: provenance of the state the request was *derived from* (i.e. the JS-side base), - on the event payload: provenance of the *resulting* state after the transition. Renaming the prop and field makes this distinction explicit at the API surface and prevents downstream consumers from conflating the two. Most other RFC-1028 deliverables are already implemented (`OnTabSelected`/`OnTabSelectionRejected`/`OnTabSelectionPrevented`, `isNativeAction`, `rejectStaleNavStateUpdates`, the prevention event). This PR is the remaining rename pass. No behavior change. ## Changes - Public TS: - type `TabsHostNavState` → `TabsHostNavStateRequest` - field `provenance` → `baseProvenance` on the request type - prop `navState` → `navStateRequest` on `TabsHostPropsBase` - re-export updated in ` src/components/tabs/index.ts` - Codegen specs (Android + iOS): same prop/field rename. The C++ Props struct regenerates automatically on next codegen run. - Native binding boundary: - Android: `setNavStateRequest` override on the ViewManager, parses `selectedScreenKey` + `baseProvenance` from the `ReadableMap`. - iOS: prop access in `RNSTabsHostComponentView::updateProps`. - Internal native state types are kept untouched — they model state, not requests: - Kotlin `TabsNavState` (data class) - ObjC `RNSTabsNavigationState` - `TabSelectOp.navState` payload (the op models a container-level state update, not a JS request) - Internal members holding the most recent JS request renamed for clarity: - Android: `jsNavState` → `jsNavStateRequest`, `updateJSNavState` → `updateJSNavStateRequest` - iOS: `_jsNavState` → `_jsNavStateRequest`, `@property navState` → `@property navStateRequest` - Apps consumer (`apps/.../TabsContainer.{tsx,types.tsx}`) updated in lockstep so the workspace stays green; helper hook renamed `useTabsHostNavState` → `useTabsHostNavStateRequest`. ## Test plan This is a pure rename — no behavior change is introduced. Verification focused on build/type integrity and a manual smoke test in `FabricExample`: - ` yarn check-types` (library) — passes. - ` yarn check-types` in `apps/` — only pre-existing, unrelated errors remain (no errors mention `navState`/`TabsHostNavState` after the rename). - Android: full rebuild of `FabricExample` — codegen regenerates `RNSTabsHostAndroidManagerInterface.setNavStateRequest(...)` and the `RNSTabsHostAndroidNavStateRequestStruct` C++ struct; the Kotlin override compiles against the new signature. - iOS: ` pod install` + build of `FabricExample` — codegen produces the renamed C++ Props member; the ObjC++ side reads ` newComponentProps.navStateRequest.{selectedScreenKey,baseProvenance}`. - Manual smoke test in `FabricExample` Tabs example: drive tab changes from JS (programmatic, via the apps' `TabsContainer`) and from native (tab bar tap). Confirm tab selection works on both platforms and ` OnTabSelected` events still fire as expected. Reviewers may want to focus on: - ` src/components/tabs/host/TabsHost.types.ts` — public type shape - both codegen specs in ` src/fabric/tabs/` - the boundary conversion in ` TabsHostViewManager.kt::setNavStateRequest` and ` RNSTabsHostComponentView.mm::updateProps` (request's `baseProvenance` is fed in as the constructor's ` provenance:` arg on the internal state types — by design) ## Checklist - [ ] Included code example that can be used to test this change. - [ ] For visual changes, included screenshots / GIFs / recordings documenting the change. - [x] For API changes, updated relevant public types. - [ ] Ensured that CI passes
1 parent 223820b commit 8c915dc

12 files changed

Lines changed: 55 additions & 52 deletions

File tree

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class TabsHost(
2929
TabsContainerDelegate,
3030
UIManagerListener {
3131
private val renderedScreens: ArrayList<TabsScreen> = arrayListOf()
32-
private var jsNavState: TabsNavState = TabsNavState.EMPTY
32+
private var jsNavStateRequest: TabsNavState = TabsNavState.EMPTY
3333

3434
private val container: TabsContainer =
3535
TabsContainer(reactContext, this).apply {
@@ -109,9 +109,9 @@ class TabsHost(
109109
container.removeAllTabsScreens()
110110
}
111111

112-
internal fun updateJSNavState(navState: TabsNavState) {
113-
jsNavState = navState
114-
container.setContainerOperation(TabSelectOp(jsNavState.copy()))
112+
internal fun updateJSNavStateRequest(navStateRequest: TabsNavState) {
113+
jsNavStateRequest = navStateRequest
114+
container.setContainerOperation(TabSelectOp(jsNavStateRequest.copy()))
115115
}
116116

117117
private val layoutCallback =

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,14 @@ class TabsHostViewManager :
7272
view.onViewManagerAddEventEmitters()
7373
}
7474

75-
override fun setNavState(
75+
override fun setNavStateRequest(
7676
view: TabsHost,
7777
value: ReadableMap?,
7878
) {
79-
val navStateMap = requireNotNull(value) { "[RNScreens] NavState must not be nullish" }
80-
val selectedScreenKey = requireNotNull(navStateMap.getString("selectedScreenKey"))
81-
val provenance = requireNotNull(navStateMap.getInt("provenance"))
82-
view.updateJSNavState(TabsNavState(selectedScreenKey, provenance))
79+
val navStateRequestMap = requireNotNull(value) { "[RNScreens] navStateRequest must not be nullish" }
80+
val selectedScreenKey = requireNotNull(navStateRequestMap.getString("selectedScreenKey"))
81+
val baseProvenance = requireNotNull(navStateRequestMap.getInt("baseProvenance"))
82+
view.updateJSNavStateRequest(TabsNavState(selectedScreenKey, baseProvenance))
8383
}
8484

8585
override fun setRejectStaleNavStateUpdates(

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { I18nManager, type NativeSyntheticEvent } from 'react-native';
33
import {
44
type TabSelectedEvent,
55
Tabs,
6-
type TabsHostNavState,
6+
type TabsHostNavStateRequest,
77
} from 'react-native-screens';
88
import type {
99
SelectTabMethod,
@@ -43,7 +43,7 @@ export function TabsContainer(props: TabsContainerProps) {
4343
determineInitialTabsContainerState,
4444
);
4545

46-
const hostNavState = useTabsHostNavState(tabsNavState);
46+
const hostNavStateRequest = useTabsHostNavStateRequest(tabsNavState);
4747

4848
const onTabSelectedCallback = React.useCallback(
4949
(event: NativeSyntheticEvent<TabSelectedEvent>) => {
@@ -74,7 +74,7 @@ export function TabsContainer(props: TabsContainerProps) {
7474

7575
return (
7676
<Tabs.Host
77-
navState={hostNavState}
77+
navStateRequest={hostNavStateRequest}
7878
onTabSelected={onTabSelectedCallback}
7979
direction={I18nManager.isRTL ? 'rtl' : 'ltr'}
8080
{...restProps}>
@@ -90,17 +90,17 @@ export function TabsContainer(props: TabsContainerProps) {
9090
);
9191
}
9292

93-
function useTabsHostNavState(
93+
function useTabsHostNavStateRequest(
9494
tabsNavState: TabsContainerState,
95-
): TabsHostNavState {
96-
const hostNavState: TabsHostNavState = React.useMemo(() => {
95+
): TabsHostNavStateRequest {
96+
const hostNavStateRequest: TabsHostNavStateRequest = React.useMemo(() => {
9797
return {
9898
selectedScreenKey: tabsNavState.suggestedState.selectedRouteKey,
99-
provenance: tabsNavState.suggestedState.provenance,
99+
baseProvenance: tabsNavState.suggestedState.provenance,
100100
};
101101
}, [tabsNavState.suggestedState]);
102102

103-
return hostNavState;
103+
return hostNavStateRequest;
104104
}
105105

106106
function useSanitizeRouteConfigs(routeConfigs: TabRouteConfig[]) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ export type TabsNavigationAction =
8080
export type TabsHostConfig = Omit<
8181
TabsHostProps,
8282
| 'children'
83-
| 'navState'
83+
| 'navStateRequest'
8484
>;
8585

8686
export type TabsContainerProps = Omit<
8787
TabsHostProps,
88-
'children' | 'navState'
88+
'children' | 'navStateRequest'
8989
> & {
9090
routeConfigs: TabRouteConfig[];
9191
/**

ios/tabs/host/RNSTabsHostComponentView.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN
5050
/**
5151
* Last navigation state requested by JS. Will be nonnull after first prop update.
5252
*/
53-
@property (nonatomic, strong, readonly, nullable) RNSTabsNavigationState *navState;
53+
@property (nonatomic, strong, readonly, nullable) RNSTabsNavigationState *navStateRequest;
5454

5555
@property (nonatomic, readonly) BOOL rejectStaleNavStateUpdates;
5656

ios/tabs/host/RNSTabsHostComponentView.mm

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ @implementation RNSTabsHostComponentView {
5353
BOOL _hasModifiedBottomAccessoryInCurrentTransation;
5454
BOOL _needsTabBarAppearanceUpdate;
5555

56-
RNSTabsNavigationState *_Nullable _jsNavState;
56+
RNSTabsNavigationState *_Nullable _navStateRequest;
5757
}
5858

5959
- (instancetype)initWithFrame:(CGRect)frame
@@ -268,14 +268,15 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props
268268
const auto &oldComponentProps = *std::static_pointer_cast<const react::RNSTabsHostIOSProps>(_props);
269269
const auto &newComponentProps = *std::static_pointer_cast<const react::RNSTabsHostIOSProps>(props);
270270

271-
if (newComponentProps.navState.selectedScreenKey != oldComponentProps.navState.selectedScreenKey ||
272-
newComponentProps.navState.provenance != oldComponentProps.navState.provenance) {
273-
NSString *selectedScreenKey = RCTNSStringFromStringNilIfEmpty(newComponentProps.navState.selectedScreenKey);
271+
if (newComponentProps.navStateRequest.selectedScreenKey != oldComponentProps.navStateRequest.selectedScreenKey ||
272+
newComponentProps.navStateRequest.baseProvenance != oldComponentProps.navStateRequest.baseProvenance) {
273+
NSString *selectedScreenKey = RCTNSStringFromStringNilIfEmpty(newComponentProps.navStateRequest.selectedScreenKey);
274274
RCTAssert(selectedScreenKey != nil, @"[RNScreens] selectedScreenKey MUST NOT be nil");
275-
RCTAssert(newComponentProps.navState.provenance >= 0, @"[RNScreens] provenance MUST BE >= 0]");
276-
_jsNavState = [RNSTabsNavigationState stateWithSelectedScreenKey:selectedScreenKey
277-
provenance:newComponentProps.navState.provenance];
278-
[_controller setPendingNavigationStateUpdate:[_jsNavState cloneState]];
275+
RCTAssert(newComponentProps.navStateRequest.baseProvenance >= 0, @"[RNScreens] baseProvenance MUST BE >= 0");
276+
_navStateRequest =
277+
[RNSTabsNavigationState stateWithSelectedScreenKey:selectedScreenKey
278+
provenance:newComponentProps.navStateRequest.baseProvenance];
279+
[_controller setPendingNavigationStateUpdate:[_navStateRequest cloneState]];
279280
}
280281

281282
if (newComponentProps.rejectStaleNavStateUpdates != oldComponentProps.rejectStaleNavStateUpdates) {

src/components/tabs/host/TabsHost.android.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function TabsHost(props: TabsHostProps) {
2525
direction,
2626
nativeContainerStyle,
2727
onTabSelected,
28-
navState,
28+
navStateRequest,
2929
...filteredBaseProps
3030
} = baseProps;
3131

@@ -41,7 +41,7 @@ function TabsHost(props: TabsHostProps) {
4141
return (
4242
<TabsHostAndroidNativeComponent
4343
style={[styles.fillParent, { direction }]}
44-
navState={navState}
44+
navStateRequest={navStateRequest}
4545
onTabSelected={onTabSelectedCallback}
4646
nativeContainerBackgroundColor={nativeContainerStyle?.backgroundColor}
4747
// @ts-ignore suppress ref - debug only

src/components/tabs/host/TabsHost.ios.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function TabsHost(props: TabsHostProps) {
2828
direction,
2929
nativeContainerStyle,
3030
onTabSelected,
31-
navState,
31+
navStateRequest,
3232
...filteredBaseProps
3333
} = baseProps;
3434

@@ -47,7 +47,7 @@ function TabsHost(props: TabsHostProps) {
4747
return (
4848
<TabsHostIOSNativeComponent
4949
style={styles.fillParent}
50-
navState={navState}
50+
navStateRequest={navStateRequest}
5151
onTabSelected={onTabSelectedCallback}
5252
nativeContainerBackgroundColor={nativeContainerStyle?.backgroundColor}
5353
// @ts-ignore suppress ref - debug only

src/components/tabs/host/TabsHost.types.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { ColorScheme, Direction } from '../../shared/types';
55

66
// #region Control
77

8-
export type TabsHostNavState = {
8+
export type TabsHostNavStateRequest = {
99
/**
1010
* @summary Valid screen key.
1111
*
@@ -14,13 +14,12 @@ export type TabsHostNavState = {
1414
*/
1515
selectedScreenKey: string;
1616
/**
17-
* @summary A number describing the provenance of the state instance.
17+
* @summary Provenance of the navigation state this request is derived from.
1818
*
1919
* @description
2020
* The provenance value establishes a relationship between different navigation state instances
21-
* held by given state holder. The assumption here is that when the navigation
22-
* state is progressed (modified), the provenance number is incremented.
23-
* This creates a relationship where we can say that:
21+
* held by given state holder. The assumption is that when the navigation state is progressed
22+
* (modified), the provenance number is incremented. This creates a relationship where we can say:
2423
*
2524
* 1. State with provenance = n + 1 has been derived from state with provenance = n.
2625
* 2. For two given navigation states A and B, we can say that A *is stale* iff
@@ -31,12 +30,15 @@ export type TabsHostNavState = {
3130
*
3231
* Currently, the native implementation of TabsHost is the state holder.
3332
*
34-
* When you use object of this shape to trigger a navigation via {@link TabsHostPropsBase#navState},
35-
* pass here THE PROVENANCE OF THE LAST ACKNOWLEDGED state you received from native side
36-
* via {@link TabsHostPropsBase#onTabSelected}. In other words this should be the provenance number
37-
* of last confirmed state you base your update request on.
33+
* Pass here THE PROVENANCE OF THE LAST ACKNOWLEDGED state you received from native side
34+
* via {@link TabsHostPropsBase#onTabSelected}. In other words this should be the provenance
35+
* number of last confirmed state you base your update request on.
36+
*
37+
* It is named `baseProvenance` (rather than `provenance`) to disambiguate it from the
38+
* `provenance` field on the {@link TabSelectedEvent} payload, which carries the provenance
39+
* of the state *resulting* from a transition.
3840
*/
39-
provenance: number;
41+
baseProvenance: number;
4042
};
4143

4244
// #endregion Control
@@ -140,15 +142,15 @@ export interface TabsHostPropsBase {
140142
* the update might get accepted or rejected.
141143
*
142144
* @see {@link TabsHostPropsBase#rejectStaleNavStateUpdates}
143-
* @see {@link TabsHostNavState} for description of the type model & accepted values.
145+
* @see {@link TabsHostNavStateRequest} for description of the type model & accepted values.
144146
*/
145-
navState: TabsHostNavState;
147+
navStateRequest: TabsHostNavStateRequest;
146148
/**
147149
* @summary If true, the native side will reject any navigation state updates coming from JS
148150
* if they are stale.
149151
*
150152
* @description A navigation state update is considered stale if it is based of an stale state
151-
* (@{link TabsHostNavState#provenance} indicates the base state).
153+
* ({@link TabsHostNavStateRequest#baseProvenance} indicates the base state).
152154
* A state is stale, when at the time of executing update, there already had been accepted a newer state
153155
* of different origin.
154156
*

src/components/tabs/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { TabsHost } from './host';
22
import { TabsScreen } from './screen';
33

44
export type {
5-
TabsHostNavState,
5+
TabsHostNavStateRequest,
66
TabSelectedEvent,
77
TabSelectionRejectedEvent,
88
TabSelectionRejectionReason,

0 commit comments

Comments
 (0)