React Navigation - custom goBack goes to different Navigator - react-native

My code setup is like so, using React Native & React Navigation v6:
Tab Navigator
- Stack A
- Screen 1
- Stack B
- Screen 1
- Stack C
- Screen 1
- Screen 2
- Screen 3
When I navigate to from Screen A1 -> C1 -> C2 and use navigation.goBack(), I end up back at screen A1 from C2. I'd expect to be go back to C1.
I'm not sure what I've done wrong here. The code:
TabNavigator.js
import 'ScreenA' from './Screens/ScreenA'
import 'ScreenB' from './Screens/ScreenB'
import { StackC } from './Stack'
const TabNavigator = () => {
return (
<Tab.Navigator initialRouteName="Home"
screenOptions={{
header: (props) => <Header {...props} />
}}
>
<Tab.Screen name="Stack A" component={ScreenA} initialParams={{ type: 'Stack A' }} />
<Tab.Screen name="Stack B" component={ScreenB} initialParams={{ type: 'Stack B' }} />
<Tab.Screen name="Stack C" component={StackC} options={{ tabBarButton: () => null }} />
</Tab.Navigator>
)
}
Stack.js
import 'ScreenC1' from './Screens/ScreenC1'
import 'ScreenC2' from './Screens/ScreenC2'
import 'ScreenC3' from './Screens/ScreenC3'
const Stack = createNativeStackNavigator()
export const StackC = () => {
return (
<Stack.Navigator screenOptions={{headerShown: false, animation: 'none'}}
initialRouteName="C1"
>
<Stack.Screen name="C1" component={ScreenC1} />
<Stack.Screen name="C2" component={ScreenC2} />
<Stack.Screen name="C3" component={ScreenC3} />
</Stack.Navigator>
)
}
EDIT: It's worth noting that I have a custom back button in the Header and when I view the routes stack there, it always prints the same output not matter where I navigate... but if I print the route stack within the C2 screen it's there. So it seems there is a disconnect between my custom back button and the actual screen.
Header.js
const Header = () => {
const routes = navigation.getState()?.routes
console.log('routes', routes.map(route => route.name) // ALWAYS prints [ A1, B1, Stack C]
return (
<View>
{navigation.canGoBack() && <Text onPress={() => navigation.goBack()}>{"< Back"}</Text >}
</View>
)
}
Navigating pages doesn't change the output in the Header but the same console.log() in screen C1 prints:
// [ C1, C2 ]
So its correct in screen C2 but not in the Header.. why is this?

Related

How to initialize Stack navigator?

I have a Bottom Tabs Navigator that contains a Stack Navigator on each tab.
const MainNavigator = createBottomTabNavigator<MainRoutes>();
export interface MainNavProps {
user: User,
signOut: SignOut
}
export function MainNav({ user, signOut }: MainNavProps) {
return (
<MainNavigator.Navigator initialRouteName="DashboardSection" screenOptions={mainScreenOptions}>
<MainNavigator.Screen name="DashboardSection" component={DashboardStack} options={dashboardOptions} />
<MainNavigator.Screen name="HashSection" component={HashStack} options={hashesOptions} />
<MainNavigator.Screen name="AccountSection" component={AccountStack} options={accountOptions} />
</MainNavigator.Navigator>
)
}
I want to emphasize here the HashStack Navigator. It consists of two screens, namely:
const HashNavigator = createStackNavigator<HashRoutes>();
const screenOptions = ({ navigation, route }: HashStackProps): StackNavigationOptions => {
const pressHandler = () => navigation.navigate("Modify", { title: "modify hashes" });
return {
...sectionScreenOptions,
headerRight: function HeaderLeft() {
return (
route.name === "Index" ?
<Button
marginR-10
iconSource={() => (
<MaterialCommunityIcons name="plus" color={Colors.primaryDarker} size={28} />
)}
style={{ width: 35, height: 35 }}
color={Colors.primaryDarker}
outline
outlineColor={Colors.primaryDarker}
onPress={pressHandler}
/> : <></>
);
},
headerTitle: route.params.title
}
}
export function HashStack() {
return (
<HashNavigator.Navigator initialRouteName="Index" screenOptions={screenOptions} >
<HashNavigator.Screen name="Index" component={Hash} initialParams={{ title: "hashes overview" }} />
<HashNavigator.Screen name="Modify" component={ModifyHash} />
</HashNavigator.Navigator>
);
}
To simplify the explanation I recorded a video:
https://user-images.githubusercontent.com/56756949/137601978-9d3aec89-3732-42a4-834b-b427bc1f73ca.mp4
When I press the tab Hashtag, which is in the middle of the screen it shows the HashStack component.
As you can see the headerRight property on the HashStack ScreenOptions is active and it shows a plus button. Clicking on the plus button it navigates to the Modify route within HashStack.
Then pressing on the tab Dashboard, which is the first Route in the Bottom Tabs Navigator and press the Hashtag tab again, it does not show the initial route screen, which is:
<HashNavigator.Screen name="Index" component={Hash} initialParams={{ title: "hashes overview" }} />
The question is, how to reset the HashNavigator when another Bottom Tab has been pressed so that the Hashtag tab always shows the initial route Index.
You can pass unmountOnBlur: true inside the tab screen options of the HashStack
const hashesOptions = {
// other options
unmountOnBlur: true
}
export function MainNav({ user, signOut }: MainNavProps) {
return (
<MainNavigator.Navigator initialRouteName="DashboardSection" screenOptions={mainScreenOptions}>
<MainNavigator.Screen name="DashboardSection" component={DashboardStack} options={dashboardOptions} />
<MainNavigator.Screen name="HashSection" component={HashStack} options={hashesOptions} />
<MainNavigator.Screen name="AccountSection" component={AccountStack} options={accountOptions} />
</MainNavigator.Navigator>
)
}

How to disable drawer inside Stack Navigator nested inside Drawer Navigator?

I have a nested stack navigator inside a drawer navigator and I don't want the user to be able to swipe from the left and open the drawer when they aren't on the first page of the stack navigator. I found some answers but they seem to use some old syntax that isn't used in the official documentation.
// stack navigator
const Items = () => {
const IStack = createStackNavigator()
return (
<IStack.Navigator initialRouteName="Items" screenOptions={{ headerShown: false }} >
<IStack.Screen name="Items" component={ItemSelect} options={{ ...TransitionPresets.SlideFromRightIOS }} />
<IStack.Screen name="Item" component={ItemScreen} options={{ ...TransitionPresets.SlideFromRightIOS }} />
</IStack.Navigator>
)
}
// drawer
const App = () => {
return (
<NavigationContainer theme={MyTheme}>
<StatusBar barStyle="light-content" backgroundColor="#232931" />
<Drawer.Navigator initialRouteName="Items">
<Drawer.Screen name="Items" component={Items} />
</Drawer.Navigator>
</NavigationContainer>
)
}
I want to disable the drawer on the second screen of the stack navigator i.e ItemScreen.
Here's what I ended up doing -
<Drawer.Screen
name="Items"
component={Items}
options={({ route }) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Items'
if (routeName == "Item")
return ({swipeEnabled: false})
}}
/>
More about getFocusedRouteNameFromRoute.
It's weird to see no answers anywhere for such a simple scenario.

In react navigation how can we replce drawer screen by stack screen where stack is in drawer? Not same screens (react native)

After successfull loging im moving to this drawer.
const SalesRepDrawerNavigation = () => {
return (
<SalesRepDrawerNavigator.Navigator initialRouteName="SalesRepDashboardStack">
<SalesRepDrawerNavigator.Screen
name="SalesRepDashboardStack"
component={SalesRepDashboardStackNavigation}
options={{
headerTitle: 'Sales Rep Dashboard'
}}
/>
<SalesRepDrawerNavigator.Screen
name="Customers"
component={CustomerListScreen}
/>
<SalesRepDrawerNavigator.Screen
name="SalesOrderView"
component={SalesOrderStackNavigation}
options={{
}}
/>
</SalesRepDrawerNavigator.Navigator>
);
};
const SalesRepDashboardStackNavigation = () => {
return (
<SalesRepDashboardStackNavigator.Navigator key="test" headerMode="none" initialRouteName="SalesRepDashboard">
<SalesRepDashboardStackNavigator.Screen
name="SalesRepDashboard"
component={SalesRepDashboardScreen}
/>
<SalesRepDashboardStackNavigator.Screen
name="CreateSalesOrder"
component={CreateSalesOrderScreen}
/>
</SalesRepDashboardStackNavigator.Navigator>
);
};
then when i go to customer list screen and select a customer I want to move to create sales order screen and at the same time want to remove customer list route. I'm using react native with react navigation v5.
How can I do this?
Use it like:
In DrawerData component you will have to create a drawer item list (a custom component) and for navigation between the screens normally use the navigation.navigate('YOUR_SCREEN_NAME');
Add all your screens in the Stack.Navigator only. Drawer will have only the Home screen.
/*Drawer data is custom drawer item list*/
export function DrawerNav() {
return (
<Drawer.Navigator drawerContent={props => <DrawerData {...props} />}>
<Drawer.Screen name="Home" component={HomeScreen} />
</Drawer.Navigator>
);
}
const RootStackScreen = () => {
return (
<Stack.Navigator>
<>
<Stack.Screen
name="HomeScreen"
component={DrawerNav}
/>
<Stack.Screen
name="FirstScreen"
component={FirstScreen}
/>
<Stack.Screen
name="SecondScreen"
component={SecondScreen}
/>
</Stack.Navigator>
);
}

Tab and Stack Navigator on same screen

When I navigate to a Tab via push both the Tab Navigator and Stack Navigator are displayed; however, when I navigate to a Stack Navigator via Tab Navigator only the Stack Navigator is displayed. How do I display both the Tab Navigator and Stack Navigator when I push to a Stack Navigator Screen? My app module:
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
function TabNavigator() {
return (
<Tab.Navigator>
<Tab.Screen name="User" component={UserDetailScreen} />
<Stack.Screen name="Feed" component={FeedScreen} />
</Tab.Navigator>
)
}
function MyStack() {
return (
<Stack.Navigator initialRouteName='Logout'>
<Stack.Screen name="Logout" component={LoginScreen} />
<Stack.Screen name="User" component={TabNavigator} />
<Stack.Screen name="UserForm" component={UserFormScreen} />
<Stack.Screen name="ItemForm" component={ItemFormScreen} />
<Stack.Screen name="Swap" component={SwapDetailScreen} />
</Stack.Navigator>
);
}
export default function App() {
console.log("test");
console.log(UserDetailScreen);
return (
<NavigationContainer>
<MyStack />
</NavigationContainer>
);
}
The TabBar is only visible when you're inside the TabNav. In your case that's either the UserDetailScreen or the FeedScreen. In order to display both, TabNav and StackNav, you need to change your nesting accordingly.
StackNav
- LoginScreen // no TabBar displayed
- TabNav
- UserDetailScreen
- FeedScreen
- UserFormScreen // no TabBar displayed
- ItemFormScreen // no TabBar displayed
- SwapDetailScreen // no TabBar displayed
Solution A: put StackNavs into your Tabs (recommended):
- TabNav
-StackNav // e.g. all user related screens
- LoginScreen
- UserDetailScreen
- UserFormScreen
-StackNav // e.g. all feed related screens
- FeedScreen
- ItemFormScreen
- SwapDetailScreen
Hint: if you want to have your login without Tabs you could exclude it from any navigator:
- LoginScreen
- TabNav
-StackNav // e.g. all user related screens
- UserDetailScreen
- UserFormScreen
-StackNav // e.g. all feed related screens
- FeedScreen
- ItemFormScreen
- SwapDetailScreen
In your code you can do sth. like this (Pseudocode):
<!-- language: lang-js -->
...
render(){
if (this.state.userIsLoggedIn){
return <TabNav />;
}
else {
return <View> My Login Screen </View>
}
}
...
Solution B: use one single Stack and customize your TabNav (might be interesting for some use cases):
- TabNav
- StackNav
- LoginScreen
- UserDetailScreen
- UserFormScreen
- FeedScreen
- ItemFormScreen
- SwapDetailScreen
In your code you can do sth. like this (Pseudocode):
...
<Tabs.Navigator tabBar = { props => <CustomTabBar {...props} /> }>
<Tabs.Screen name="TabBar" component={ getScreenStack } />
</Tabs.Navigator>
...
const CustomTabBar = ({ navigation }) => {
return (
<View>
<Button
title="User"
onPress={() => { navigation.navigate('UserScreen') }}
/>
)
}
...
function getScreenStack(){
return (
<Stack.Navigator>
<Stack.Screen name="Login” component=”LoginScreen” />
...
</Stack.Navigator>
)
}
...
If performance is not an issue I would stick to solution A, because solution B has several drawbacks:
Tab Indicators need to be tracked manually
Back button behaviour is odd and needs to be adjusted, because Tabs are handled as part of Stack
Hope that helps.
Why your FeedScreen use <Stack.Screen/> instead of Tab.Screen/>?
<Tab.Navigator>
<Tab.Screen name="User" component={UserDetailScreen} />
<Stack.Screen name="Feed" component={FeedScreen} />
</Tab.Navigator>
I am confused about this, it may be the problem.

React Native StackNavigator: Passing props from a screen to the header of the next screen

I have a React Native StackNavigator like so:
const AppStack = () => {
return (
<NavigationContainer theme={{ colors: { background: "white" }}}>
<Stack.Navigator headerMode="screen">
<Stack.Screen name="Master" component={ Master } />
<Stack.Screen
name="Details"
component={ Details }
options={{ headerTitle: props => <Header {...props} /> }} // <-- how can I pass props from Master to the Header here
/>
</Stack.Navigator>
</NavigationContainer>
)
}
The navigation works fine. When I'm on Master, if I press a TouchableOpacity, it brings up Details, with the header component Header.
However, what I want is to pass props from Master to the Header component in Details.
Something like this:
{/* What I want is that onPress, I want to pass someones_name and someones_photo_url to
the Details' Header component */ }
const Master = () => {
const someones_name = "Steve";
const someones_photo_url = "http://somephoto.com/001.jpg";
return (
<TouchableOpacity onPress={() => navigation.navigate("Details")}>
<Text>{ someones_name }</Text>
<Image source={{ uri: someones_photo_url }}>
</TouchableOpacity>
)
}
Is this possible?
You can pass values to other screens when navigating by doing the following:
Assume I am on screen "Feed" and click on someones name thus I want to go to screen "Profile" where it will show that user name that I clicked on.
In screen Feed
props.navigation.navigate('Profile, { userName: 'Shane' })`
In screen Profile I can grab that value passed in via navigate with:
const { userName } = props.route.params
To set this up inside a header assuming your screen options are outside the screen component and cannot access the props. Look into using setOptions to configure your header title dynamically.
props.navigation.setOptions({
headerTitle: ...
})