Navigate to the screen when Tab on BottomTabNavigator is pressed - react-native

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} />

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 to know which Stack.Screen I'm on?

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.

React Navigation stop back button behaviour on bottom bar

Hey I am using the createBottomTabNavigator() from React Navigation and I have a custom topbar which I update with my own states. The problem is when I click on a Tab the listener gets called with every navigation so the topbar gets into the wrong state if you for example go to screen 2 the topbar changes for screen 2. When I press on the topbar back button it goes back to screen 1 and changes the topbar to screen 1 topbar. Now if I use the bottombar tab instead to navigate back instead of using the topbar one it keeps the topbar from screen 2. So my question is if I can disable somehow that if I press on the bottom bar that it navigates back to the initial screen 1.
<Tab.Screen
listeners={() => {
switchTab(0);
}}
name="Input"
children={() => (
<SuchStackContainer
ref={suchStackRef}
switchStack={switchStack}
listings={listings}
setListings={setListings}
/>
)}
options={{
tabBarLabel: 'Suche',
tabBarIcon: ({color, size}) => (
<Ionicons name="car" color={color} size={size} />
),
}}
/>
I am supposing you are using Bottom Tabs Navigator from React Navigation. As mentioned in the docs you can override onPress method and navigate it to the desired tab. These can be specified under screenOptions prop of Tab.navigator or options prop of Tab.Screen. Let me know if you still need my help
function MyTabBar({ navigation }) {
return (
<Button
title="Go somewhere"
onPress={() => {
// Navigate using the `navigation` prop that you received
navigation.navigate('SomeScreen');
}}
/>
);
}

how to dispatch an action on react navigation custom tab bar component

I want to dispatch an action on of the buttons on custom my tab bar navigator and due to react navigation docs we can not use hooks in custom tab bar component
. Any one has an idea to do that?
first you need to prevent the default action of tab bar , and after perventing you can call your own function or any component.
<Tab.Screen
name="More"
component={Sleep}
options={{
tabBarLabel: <Text style={stylee.Fontfamily}>More</Text>,
tabBarIcon: ({color}) => (
<Icon name="menu-outline" color={color} size={ms(23)} />
),
}}
listeners={({navigation}) => ({
tabPress: event => {
event.preventDefault(); //preventing dafault.
navigation.openDrawer(); //calling custom
},
})}
/>
</Tab.Navigator>
keep in mind , you need to give a component( it's ok if the component is empty or just a text).