How to know which Stack.Screen I'm on? - react-native

I have this Tab Screen.
<Tabs.Screen
name="Photo"
options={{
tabBarIcon: ({ focused, color, size }) => null,
}}
>
{() => <StackNavFactory screenName="Photo" />}
As you can see if I click Photo tab, It goes to 'StackNavFactory screenName=Photo'.
StackNavFactory has these two Stack screens.
It has Photo stack screen and Comments stack screen.
{screenName === "Photo" ? (
<Stack.Screen name="Photo" component={Photo} />
) : null}
<Stack.Screen
name="Comments"
component={Comments}
options={{
headerTintColor: "white",
}}
/>
Since It should go to screenName="Photo" first, when I click Photo tab, It goes to Photo Stack screen.
But if I click some button on Photo screen, I set It goes to Comments Stack screen on the same Photo tab.
In summary Photo tab has two stack screen Photo and Comments.
And I want to know where I'm on photo or Comments from TabBar components.

There are several ways to get the focused route name. In this use case, you can use the getFocusedRouteNameFromRoute(route) function.
(Untested) example:
<Tabs.Navigator
screenOptions={({ route }) => ({
tabBarStyle: {
display: getFocusedRouteNameFromRoute(route) === "..." ? "..." : "...",
},
})}
>
...
</Tabs.Navigator>
Also, you can read Setting parent screen options based on the child navigator's state in the official documentation to learn more.

Related

Share Screens between Tab Navigator and Drawer Navigator while preserving state

My app has 3 main screens (each of which is a Stack Navigator):
MessagesStack
ExpensesStack
CalendarStack
It also has 2 supplemental screens (again, both stacks):
DashboardStack
DocumentsStack
I want to combine a drawer navigator and a tab navigator, but with some overlap. In particular, what I'm trying to achieve is for the drawer navigator to include links to all of the stacks, while the bottom tab navigator contains links only to the three main stacks. The tab navigator should always be visible, on every screen - essentially a sticky footer that enables the user to quickly jump to any of the three main screens. So the layout is like this:
--- Messages
--- Expenses
--- Calendar
--- Dashboard
--- Documents
| Messages | Expenses | Calendar |
Pressing the Messages item in the drawer nav should take the user to the same view as pressing the Messages item from the tab nav. It's important that the state of that view be preserved. In particular, suppose the user opened a specific message in the messages view. This takes them to a MessageDetail screen inside the Messages stack. If they navigate away from the Messages stack and then navigate back, they should still see the MessageDetail screen no matter what navigation route they took.
I've tried a few approaches, but none of them quite work. The closest I got was something like this:
function BottomTabNavigator({ initialRouteName }) {
return (
<BottomTab.Navigator initialRouteName={props.initialRouteName}>
<BottomTab.Screen name="MessagesTab" component={MessagesStackNavigator} />
<BottomTab.Screen name="ExpensesTab" component={ExpensesStackNavigator} />
<BottomTab.Screen name="CalendarTab" component={CalendarStackNavigator} />
<BottomTab.Screen
name="DashboardTab"
component={DashboardStackNavigator}
options={{
tabBarShowLabel: false,
headerShown: false,
tabBarButton: () => <></>,
}}
/>
<BottomTab.Screen
name="DocumentsTab"
component={DocumentsStackNavigator}
options={{
tabBarShowLabel: false,
headerShown: false,
tabBarButton: () => <></>,
}}
/>
</BottomTab.Navigator>
);
}
Then my root navigator was like this:
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen
name="MessagesDrawer"
options={{ title: "Messages" }}
children={() => <BottomTabNavigator initialRouteName="MessagesTab" />}
/>
<Drawer.Screen
name="ExpensesDrawer"
options={{ title: "Expenses" }}
children={() => <BottomTabNavigator initialRouteName="ExpensesTab" />}
/>
<Drawer.Screen
name="CalendarDrawer"
options={{ title: "Calendar" }}
children={() => <BottomTabNavigator initialRouteName="CalendarTab" />}
/>
<Drawer.Screen
name="DashboardDrawer"
options={{ title: "Dashboard" }}
children={() => <BottomTabNavigator initialRouteName="DashboardTab" />}
/>
<Drawer.Screen
name="DocumentsDrawer"
options={{ title: "Documents" }}
children={() => <BottomTabNavigator initialRouteName="DocumentsTab" />}
/>
</Drawer.Navigator>
</NavigationContainer>
Unfortunately, this creates 5 independent TabNavigators which don't share state. So if e.g. the user navigates to Drawer(Messages) -> Tab(Calendar), then navigates away, the next time they tap on Drawer(Messages) it will take them to the Calendar tab! If I set unmountOnRender={true} it solves that problem: tapping Drawer(Messages) will now always open the Messages tab. But the stack state is now lost, so when e.g. the user opens a message, navigates away, and comes back, the message is no longer open.
If it's possible to achieve what I'm trying to do here declaratively that would be ideal, but even if the only way is imperative (e.g. with some navigation.jumpTo calls inside effects in each screen component) that would still be ok. I've tried some imperative approaches but nothing that works yet. Any help is greatly appreciated.

React Native - Bottom Tab Navigator - error - Passing an inline function will cause the component state to be lost on re-render?

I have a stack navigator which contains a Bottom tab navigator.
The first tab of the bottom tab navigator further contains a TopTab Navigator .
This is being done to display a Top Tab as well as a Bottom tab.
Each of the other bottom tab screens open a new screen .
Here is the code : ( stack navigator containing Bottom Tab Navigator )
const StackNav = createNativeStackNavigator();
function Main() {
return (
<NavigationContainer>
<StackNav.Navigator>
<StackNav.Screen
name="BottomTab"
component={BottomTabScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerBlurEffect: 'dark',
}}
/>
</StackNav.Navigator>
</NavigationContainer>
);
}
The Bottom Tab Navigator is below ( first tab contains an embedded Top Tab Navigator TopTabScreen to display header , rest of the tabs show tabs on the bottom )
const BottomTabScreen = () => {
return (
<BottomNav.Navigator barStyle={{backgroundColor: '#febe00'}}>
<BottomNav.Screen
name="Home"
component={TopTabScreen}
options={{
tabBarIcon: ({color, size}) => (
<MaterialCommunityIcons name="home" color={color} size={26} />
),
labelStyle: {textTransform: 'none'},
upperCaseLabel: false,
}}
/>
<BottomNav.Screen
name="MyInitiatives"
component={InitiativesScreen}
options={{
tabBarIcon: ({color, size}) => (
<IonIcons name="rocket-outline" color={color} size={26} />
),
}}
/>
<BottomNav.Screen
name="Contact-Whatsapp"
component={() => null}
listeners={{
tabPress: (e) => {
e.preventDefault();
console.log("tabPress tabTwo");
let link = 'https://wa.me/xxx';
Linking.openURL(link);
},
}}
options={{
tabBarIcon: ({color,size}) => (
<FontAwesome name="whatsapp" color={color} size={26} />
),
}}
/>
</BottomNav.Navigator>
);
};
So on bottom tab - three tabs will show :
Home - tab which shows the embedded header
MyInitiatives - shows a separate view when clicked
Contact-Whatsapp - this is a tab which I am trying to show which when clicked should open whats
app and NOT a new screen , so on clicking the tab - I dont want any
'component' to render , rather simply whats app to open
Note - whatsapp is opening but am getting this warning :
WARN Looks like you're passing an inline function for 'component' prop for the screen
'Whatsapp'
(e.g. component={() => <SomeComponent />}). Passing an inline function will cause the
component state to be lost on re-render and cause perf issues since it's re-created every
render. You can pass the function as children to 'Screen' instead to achieve the desired
behaviour.
So questions are :
#1 how do I get around this issue ?
how do I ensure that on clicking on bottom tab - it should open the link ( in this case - whatsapp ) rather than try and open a component / view ?
The reason for the warning is that you are passing a function to the component prop rather than the name of the function. You can put any component name in there as with the e.preventDefault() it will not be opened. You can just create a small dummy component and pass in the name. Even component={View} should do the job.

How do I add a navigation button to a React Navigation Stack header with nested Bottom Tab Navigator?

I am trying to build a mobile app in react-native and I'm having some problems setting up React Navigation.
What I want to achieve is a Bottom Tab Navigator that Navigates to the 'Home' screen and the 'Profile' Screen. From the 'Home' screen, there should be a button to navigate to the 'Settings' screen in the Header.
I have got to the point where I have a Bottom Tab Navigator that can successfully navigate between the 'Home' and 'Profile' screens, as well as a button on the header for the Settings screen using the Stack navigation header. However, I am having trouble navigating to the 'Settings' screen with this button.
My code for the Stack navigator is:
const MainStackNavigator = () => {
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen
name="Home"
component={HomeScreen}
options = { ({navigation}) => ({
title: "Home",
headerStyle: {
backgroundColor: '#ff6600',
},
headerRight: () => (
<Button
onPress={() => navigation.navigate(SettingScreen)}
title="Settings"
color="#fff"
/>
)
})}
/>
<Stack.Screen name="Settings" component={SettingScreen} />
</Stack.Navigator>
);
}
When I click on the Settings button, I get the error:
"The action 'NAVIGATE' with payload undefined was not handled by any navigator.
Do you have a screen named 'SettingScreen'?"
Upon looking for a solution to this error I found this article: Nesting Navigators
It recommends keeping nested navigators to a minimal. Is my method even the right way about going for this UI design? Is there a way to achieve this with only using one navigator?
After some time trying to solve this I found the problem was quite silly of me. navigation.navigate takes the name of the screen to navigate to, but I was giving it the component.
To fix the problem I changed
onPress={() => navigation.navigate(SettingScreen)}
to
onPress={() => navigation.navigate('Settings')}
Add this below your render method!
render () {
const { navigate } = this.props.navigation;
}
And then in the onPress
onPress={() => navigate(SettingScreen)}
Hopefully this helps

Navigate to the screen when Tab on BottomTabNavigator is pressed

I would like to navigate to the screen when the particular tab on the BottomTabNavigator is pressed.
Normally, when the tab is pressed, it navigates to the configured screen automatically. But I don't want to have that behaviour. I want to hide the bottom tab on that screen and provide back feature in the top bar too. I normally use navigation.navigate('routeName') in ReactNavigationStack screens. But I don't know how/where to write this code in the BottomTabNavigator configuration.
For example, I've got the following 5 tabs in the bottom bar. I want to navigate to AddNewScreen when Add button is pressed. I don't know where to put that onPress event. I tried to put it under options and BottomTab.Screen. But still no luck.
I tried to intercept onPress event to use navigation.navigate. But it's not even hit and it always opens the AddNewScreen with the tab bar.
<BottomTab.Navigator initialRouteName={INITIAL_ROUTE_NAME}>
<BottomTab.Screen
name="Home"
component={HomeScreen}
initialParams="Home Params"
options={{
title: 'Home',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-home" iconType="ion" />,
}}
/>
<BottomTab.Screen
name="AddNew"
component={AddNewScreen}
options={{
title: 'Add',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-add-circle" iconType="ion"
onPress={(e) => {
e.preventDefault();
console.log(e)
}} />,
}}
/>
</BottomTab.Navigator>
The Add new screen is always opened with the bottom tab bar.
Questions:
Is there anyway to navigate to specific screen when the tab is
pressed?
Is there anyway to hide the bottom tab bar on that Add New
Screen?
Update:
The Navigation library v6 supports the Listener feature that can be used
<Tab.Screen
name="Chat"
component={Chat}
listeners={{
tabPress: e => {
// Prevent default action
e.preventDefault();
//Any custom code here
alert(123);
},
}}
/>;
You can have a custom functionality in the bottom toolbar using the tabbar button. The code would be like below
<Tab.Screen
name="Settings2"
component={SettingsScreen}
options={{
tabBarButton: props => (
<TouchableOpacity {...props} onPress={() => alert(123)} />
),
}}
/>
This would render a normal bottom tab bar button but the onclick would show the alert, you can replace the code with your navigate or any other code you need.
Also the 'SettingsScreen' component can be a dummy component returning null.
Hope this helps.
You can have a custom functionality
<Tab.Screen
name="Add"
component={View}
listeners={({ navigation }) => ({
tabPress: (e) => {
// Prevent default action
e.preventDefault();
// Do something with the `navigation` object
navigation.navigate("PhotoNavigation"); // Here!!!!!!!!!!!!!!!!!!!!!!!!!!!!
},
})}
/>
<Tab.Screen name="Notifications" component={Notifications} />

React native: How can I have multiple drawer navigator links point to screens within the same stack navigator

I am new to react native and I haven't seen this question asked by anyone or haven't found a way around this.
Using react navigation 5 with expo.
Currently I have a the following app structure:
Stack navigator inside of drawer navigator.
Example of page structure:
Drawer Navigator ( links ):
Home (RouteStack)
Screen 1
Screen 2
Screen 3
RouteStack( screens) :
Home ( initial route )
Screen 1
Screen 2
Screen 4
How can I get Screen 1/Screen 2 link in drawer navigator load RouteStack: Screen 1/Screen 2?
These links are provided to easily jump to the required screen.
Need some guidance on how to achieve this.
I have thought of the possibility of drawer inside of stack, but there are screens inside of drawer that may not be listed in the stack. Hence, went with stack inside of drawer.
I have also tried to do a navigation.navigate(route.name) inside of RouteStack
Sample code:
Drawer navigator:
<NavigationContainer>
<Drawer.Navigator drawerContent={(props, navigation) => <CustomDrawerContent {...props} {...navigation} />}>
<Drawer.Screen name="Home" component={RouteStack} />
<Drawer.Screen name="MyItems" component={RouteStack} />
<Drawer.Screen name="ContactRep" component={RouteStack} />
<Drawer.Screen name="Settings" component={SettingInfo} />
</Drawer.Navigator>
</NavigationContainer>
Stack navigator (RouteStack) looks like this:
<Stack.Navigator
initialRouteName="Home"
screenOptions={{ gestureEnabled: false, headerTitleAlign: 'auto' }}
// headerMode="float"
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
title: '',
headerStyle: {
backgroundColor: '#fff',
},
headerTintColor: '#000',
headerTitleStyle: {
fontWeight: 'bold'
},
headerLeft: props => <HeaderLeftMenu {...props} />,
headerRight: props => <HeaderRightMenu {...props} />,
headerTitle: props => <HeaderTitle {...props} />
}}
/>
<Stack.Screen
name="ContactRep"
component={ContactRep}
options={{ headerTitle: props => <HeaderTitle {...props} /> }}
/>
<Stack.Screen
name="MyItems"
component={MyItems}
options={{ headerTitle: (props, navigation) => <HeaderTitle {...props} /> }}
/>
</Stack.Navigator>
Thanks in advance and help is appreciated.
Your method is fine. But to clarify your ideas I will give you an example.
Assume I have a main Drawer. In that drawer I can navigate to 2 different screens. Inside those screens, I can navigate and do diferent things (like going to sub-screens), but never go outside the drawer.
To do this, we would have to created nested navigators. This meaning, one type of navigator if going to be inside another one. In our case of example:
<Papa Drawer>
<Screen 1 component={StackSon1}>
<Screen 2 component={StackSon2}>
<Papa Drawer>
And then StackSon1, for example, will look like this:
StackSon = () => {
return (
<Stack.Navigator>
<Stack.Screen>
<Stack.Screen>
...
)
}
React Navigation will also handle every drawer separately, meaning that you don't have to worry about the user creating an infinite chain of open screens.
Also, remember that, when we Nest navigators using a function (like I did) we must use return (or the simplified version of return with just parenthesis)
Hope it helps.