diff --git a/example/src/Screens/BottomTabs.tsx b/example/src/Screens/BottomTabs.tsx index 90f3f5eb..3ccb2799 100644 --- a/example/src/Screens/BottomTabs.tsx +++ b/example/src/Screens/BottomTabs.tsx @@ -2,6 +2,11 @@ import * as React from 'react'; import { View, ScrollView, StyleSheet, Platform } from 'react-native'; import { Button } from 'react-native-paper'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import { + getFocusedRouteNameFromRoute, + ParamListBase, +} from '@react-navigation/native'; +import { StackScreenProps } from '@react-navigation/stack'; import { createBottomTabNavigator, BottomTabNavigationProp, @@ -59,7 +64,18 @@ const AlbumsScreen = ({ const BottomTabs = createBottomTabNavigator(); -export default function BottomTabsScreen() { +export default function BottomTabsScreen({ + navigation, + route, +}: StackScreenProps) { + const routeName = getFocusedRouteNameFromRoute(route) ?? 'Article'; + + React.useLayoutEffect(() => { + navigation.setOptions({ + title: routeName, + }); + }, [navigation, routeName]); + return ( { + expect(getFocusedRouteNameFromRoute({ name: 'Home' })).toBe(undefined); +}); + +it('gets focused route name from nested state', () => { + expect( + getFocusedRouteNameFromRoute({ + name: 'Home', + state: { + routes: [{ name: 'Article' }], + }, + }) + ).toBe('Article'); + + expect( + getFocusedRouteNameFromRoute({ + name: 'Home', + state: { + index: 1, + routes: [{ name: 'Article' }, { name: 'Chat' }, { name: 'Album' }], + }, + }) + ).toBe('Chat'); + + expect( + getFocusedRouteNameFromRoute({ + name: 'Home', + state: { + routes: [{ name: 'Article' }, { name: 'Chat' }], + }, + }) + ).toBe('Chat'); + + expect( + getFocusedRouteNameFromRoute({ + name: 'Home', + state: { + type: 'tab', + routes: [{ name: 'Article' }, { name: 'Chat' }], + }, + }) + ).toBe('Article'); +}); + +it('gets nested screen in params if present', () => { + expect( + getFocusedRouteNameFromRoute({ + name: 'Home', + params: { screen: 'Chat' }, + }) + ).toBe('Chat'); + + expect( + getFocusedRouteNameFromRoute({ + name: 'Home', + params: { screen: 'Chat', initial: false }, + }) + ).toBe('Chat'); + + expect( + getFocusedRouteNameFromRoute({ + name: 'Home', + params: { screen: {} }, + }) + ).toBe(undefined); +}); diff --git a/packages/core/src/getFocusedRouteNameFromRoute.tsx b/packages/core/src/getFocusedRouteNameFromRoute.tsx new file mode 100644 index 00000000..d4a94c77 --- /dev/null +++ b/packages/core/src/getFocusedRouteNameFromRoute.tsx @@ -0,0 +1,29 @@ +import { + Route, + PartialState, + NavigationState, +} from '@react-navigation/routers'; + +export default function getFocusedRouteNameFromRoute( + route: Partial> & { state?: PartialState } +): string | undefined { + const state = route.state; + const params = route.params as { screen?: unknown } | undefined; + + const routeName = state + ? // Get the currently active route name in the nested navigator + state.routes[ + // If we have a partial state without index, for tab/drawer, first screen will be focused one, and last for stack + // The type property will only exist for rehydrated state and not for state from deep link + state.index ?? + (typeof state.type === 'string' && state.type !== 'stack' + ? 0 + : state.routes.length - 1) + ].name + : // If state doesn't exist, we need to default to `screen` param if available + typeof params?.screen === 'string' + ? params.screen + : undefined; + + return routeName; +} diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index 14931856..193acb22 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -20,4 +20,6 @@ export { default as getStateFromPath } from './getStateFromPath'; export { default as getPathFromState } from './getPathFromState'; export { default as getActionFromState } from './getActionFromState'; +export { default as getFocusedRouteNameFromRoute } from './getFocusedRouteNameFromRoute'; + export * from './types';