I am in the process of implementing drawer navigation with nested tab navigation within my app.
At present I have two issues that I cannot work out so far and am looking for pointers. I am using React Navigation v6.
Upon clicking any of my tab navigator links I notice that my screen title is always set to "Home". Can this be updated to the current selected tab?
The last clicked tab appears to keep its state when using the drawer links, the journey would look like
Start on home page (Renders home screen fine)
click userprofile tab (Renders user profile page fine)
then click "Book New Class" from within the Drawer, (Renders New Class page fine)
then click "Home" from within the Drawer (This renders the user profile screen)
I have created a snack that will hopefully show the issues I'm facing https://snack.expo.dev/#richlewis14/drawer-and-tab-navigation
DrawerNavigator.js
import React, {useContext} from 'react';
import CustomDrawer from '../components/CustomDrawer.js';
import {BookNewEvent} from '../screens/events/BookNewEvent.js';
import {UserProfile} from '../screens/profile/UserProfile.js';
import BottomTabNavigator from '../navigation/BottomTabNavigator.js';
import {createDrawerNavigator} from '#react-navigation/drawer';
import Ionicons from 'react-native-vector-icons/Ionicons';
const Drawer = createDrawerNavigator();
const DrawerNavigator = ({navigation}) => {
return (
<Drawer.Navigator
drawerContent={props => <CustomDrawer {...props} />}
screenOptions={{
drawerActiveBackgroundColor: '#2B6EB5',
drawerActiveTintColor: '#fff',
drawerInactiveTintColor: '#333',
drawerLabelStyle: {marginLeft: -25, fontSize: 15},
}}>
<Drawer.Screen
name="Home"
component={BottomTabNavigator}
options={{
title: 'Home',
drawerIcon: ({color}) => <Ionicons name="home" size={22} color={color} />,
}}
/>
<Drawer.Screen
name="BookNewEvent"
component={BookNewEvent}
options={{
title: 'Book Class',
drawerIcon: ({color}) => <Ionicons name="calendar-outline" size={22} color={color} />,
}}
/>
</Drawer.Navigator>
);
};
export default DrawerNavigator;
BottomTabNavigator.js
import React, {useContext} from 'react';
import {UserLandingPage} from '../screens/landing/UserLandingPage.js';
import {BookNewEvent} from '../screens/events/BookNewEvent.js';
import {UserProfile} from '../screens/profile/UserProfile.js';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import {createNativeStackNavigator} from '#react-navigation/native-stack';
import Ionicons from 'react-native-vector-icons/Ionicons';
const Tab = createBottomTabNavigator();
const Stack = createNativeStackNavigator();
const UserHomeStack = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={UserLandingPage}
options={{headerShown: false}}
/>
</Stack.Navigator>
);
};
const BottomTabNavigator = ({navigation}) => {
return (
<Tab.Navigator
screenOptions={{
headerShown: false,
tabBarShowLabel: false,
}}>
<Tab.Screen
name="Home2"
component={UserHomeStack}
options={{
tabBarIcon: () => <Ionicons name="home" size={22} />,
}}
/>
<Tab.Screen
name="UserBookNewEvent"
component={BookNewEvent}
options={{
tabBarIcon: () => <Ionicons name="add-circle" size={22} />,
}}
/>
<Tab.Screen
name="UserProfilePage"
component={UserProfile}
options={{
tabBarIcon: () => <Ionicons name="person" size={22} color="black" />,
}}
/>
</Tab.Navigator>
);
};
export default BottomTabNavigator;
You are nesting a Tab.Navigator inside a Drawer.Navigator. Compare this with the following code snippet from your snack.
<Drawer.Screen
name="Home"
component={BottomTabNavigator}
options={{
title: 'Home',
drawerIcon: ({color}) => <Ionicons name="home" size={22} color={color} />,
}}
/>
This is not a problem but you need to keep in mind that nesting navigators change certain behaviors of your application, e.g.
Each navigator has its own options.
For example, specifying a title option in a screen nested in a child navigator won't affect the title shown in a parent navigator.
We can fix this by using the code provided in the official documentation of react-native for setting parent screen options based on child navigators state.
Here is what I have done in order to fix your title issue. In BottomTabNavigator.js:
const BottomTabNavigator = ({navigation, route}) => {
function getHeaderTitle(route) {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
switch (routeName) {
case 'Home2':
return 'Home';
case 'UserBookNewEvent':
return 'Book Class';
case 'UserProfilePage':
return 'Profile';
}
}
React.useLayoutEffect(() => {
navigation.setOptions({ headerTitle: getHeaderTitle(route) });
}, [navigation, route]);
...
Notice that our BottomTabNavigator is a child of the Drawer for the Home-Screen, thus it seems that both navigator navigate to the same screen while updating each others navigation states. This is not possible. We can see this, if we navigate via the Drawer to a different screen, then its title is now updated, however, the screen stays the same.
If it is desired to navigate via the Tab.Navigator if the user is on the Home-screen, such that both navigators stay visible, then this is possible. If the user navigates to a screen, different then the Home- screen via the Drawer, then the Tab.Navigator will vanish.
Thanks to the discussion from the comments, this is acceptable. Solving your second issue can be done with a little hack by setting the prop unmountOnBlur to true for the Home-screen provided in the DrawerNavigator as follows.
<Drawer.Screen
name="Home"
component={BottomTabNavigator}
options={{
title: 'Home',
unmountOnBlur:true,
drawerIcon: ({color}) => <Ionicons name="home" size={22} color={color} />,
}}
/>
Please consider my updated snack for a full working solution.
Related
I'm new to React Native and I have a very simple first page that I'm testing out. My only thing I'm having a hard time understanding is where there is a Title view at the top area. Here's what my app currently looks like https://i.stack.imgur.com/H7vbO.png As you can see, there's that huge white space that has "Home" in it. I'm trying to get rid of it completely and just have the my background color throughout the whole thing. My only problem is that I don't even know where that is coming from. Here is my code and thank you.
This is my HomeScreen
import React from "react";
import { StyleSheet, Text, SafeAreaView, Platform } from "react-native";
import { StatusBar } from "expo-status-bar";
const HomeScreen = ({}) => {
return (
<SafeAreaView style={styles.container}>
<Text style={{ color: "white" }}>Home Screen</Text>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#4B4B4B",
alignItems: "center",
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0,
},
});
export default HomeScreen;
This is my App.js
import React from "react";
import { NavigationContainer } from "#react-navigation/native";
import { createNativeStackNavigator } from "#react-navigation/native-stack";
import MainTabScreen from "./screens/MainTabScreen";
const Stack = createNativeStackNavigator();
const App = () => {
return (
<NavigationContainer>
<MainTabScreen />
</NavigationContainer>
);
};
export default App;
Here is my MainTabScreen
import React from "react";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import Icon from "react-native-vector-icons/Ionicons";
import HomeScreen from "./HomeScreen";
import SearchScreen from "./SearchScreen";
import InboxScreen from "./InboxScreen";
import AccoutScreen from "./AccountScreen";
const Tab = createBottomTabNavigator();
const MainTabScreen = () => {
return (
<Tab.Navigator initialRouteName="Home" activeColor="#000000">
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
tabBarIcon: ({ color }) => (
<Icon name="ios-home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Search"
component={SearchScreen}
options={{
tabBarIcon: ({ color }) => (
<Icon name="ios-search" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Inbox"
component={InboxScreen}
options={{
tabBarIcon: ({ color }) => (
<Icon name="ios-chatbox" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Account"
component={AccoutScreen}
options={{
tabBarIcon: ({ color }) => (
<Icon name="ios-person" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
};
export default MainTabScreen;
P.S.: I already tried changing SafeAreaView to just View, and I also try removing the padding top
That view is actually the header of the navigator. Thus, to remove it, on your main navigator, set headerShown: false.
In your mainTabScreen;
<Tab.Navigator screenOptions={{headerShown: false}} initialRouteName="Home" activeColor="#000000">
...
Rest will be same.
Bottom tab accepts header related options in options property of Navigator Screens and screenOptions property in The Navigator Component
e.g
const MainTabScreen = () => {
return (
<Tab.Navigator
initialRouteName="Home"
activeColor="#000000"
// To remove header from all screens,
// screenOptions={{ headerShown: false }}
>
<Tab.Screen
name="Home"
component={Home}
options={{
tabBarIcon: ({ color }) => (
<Icon name="ios-home" color={color} size={26} />
),
// Update Header Title
// headerTitle: 'NOT A HOME',
}}
/>
<Tab.Screen
name="Search"
component={Home}
options={{
tabBarIcon: ({ color }) => (
<Icon name="ios-search" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
};
You can read about all header related options in - https://reactnavigation.org/docs/elements#header
I'm working on a simple app and have now added drawer navigation, but I'm having a weird problem with the drawer that opens again after navigating to the screen from the drawer content.
PlayerZone is a BottomTabNavigation that contains Profile and other screens.
I tried to dispatch close drawer action before and after navigate to screen, also I tried requestAnimationFrame but nothing helps.
how it looks like
DrawerScreens:
<Animated.View style={[styles.stack, style]}>
<Stack.Navigator
screenOptions={{
header: () => null,
}}>
<Stack.Screen name="PlayerZone" component={PlayerZone} />
</Stack.Navigator>
<GameBottomNavigation />
</Animated.View>
Drawer:
<DrawerItem
label="Profile"
style={styles.item}
labelStyle={styles.itemLabel}
onPress={() => {
props.navigation.navigate('PlayerZone', {
screen: 'Profile',
id: null,
});
}}
/>
<DrawerItem
label="Items"
style={styles.item}
labelStyle={styles.itemLabel}
onPress={() => {
props.navigation.navigate('PlayerZone', {
screen: 'Profile',
id: 'some-id',
});
}}
/>
PlayerZone:
<Tab.Navigator
initialRouteName="Profile"
screenOptions={{
header: () => null,
}}>
<Tab.Screen name="Profile" component={Profile} />
{!peeking && <Tab.Screen name="Items" component={Items} />}
</Tab.Navigator>
Items:
import React, {FC} from 'react';
import {BottomTabScreenProps} from '#react-navigation/bottom-tabs';
import Block from '../../components/Block';
import Text from '../../components/Text';
import {PlayerZoneTabParamList} from '.';
type Props = BottomTabScreenProps<PlayerZoneTabParamList, 'Items'>;
const Items: FC<Props> = () => {
return (
<Block middle center>
<Text>Items</Text>
</Block>
);
};
export default Items;
So after some time trying everything I could think of I found solution (kind of). In drawer navigator I had props useLegacyImplementation set to false because I have configured reanimated 2. I removed this props and everything start working properly.
I am making use of bottom navigation in React Native and I have a header which is supposed to show on which screen I am at the moment. I am not sure how to change that text on Tab Bar press. I tried making use of onPress event but seems like it does not work.
Here is my code:
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<View style={styles.header}>
<Text style={styles.headerText}>{headerText}</Text>
<SettingsIcon />
</View>
<View style={styles.main}>
<NavigationContainer >
<Tab.Navigator initialRouteName="Home" tabBarOptions={{
activeTintColor: '#FF9F0A',
inactiveTintColor:'white',
style: {
backgroundColor: '#000000',//color you want to change
}
}}>
<Tab.Screen name="Home" component={Home} options={{
tabBarLabel: 'HOME',
tabBarIcon: ({ color, size }) => (
<HomeTabIcon name="home" color={color} size={size} />
),
}}/>
<Tab.Screen name="Controls" component={Controls} options={{
tabBarLabel: 'CONTROLS',
tabBarIcon: ({ color, size }) => (
<ControlsTabIcon name="controls" color={color} size={size} />
),
}}/>
<Tab.Screen name="Charging" component={Charging} options={{
tabBarLabel: 'CHARGING',
tabBarIcon: ({ color, size }) => (
<ChargingTabIcon name="charging" color={color} size={size} />
),
}}/>
</Tab.Navigator>
</NavigationContainer>
</View>
</View>
you can nest your tab navigator in a screen navigator.
You can change the title of your screen navigator header in function of the name of your active screen.
for more information read this.
import React from 'react';
import { NavigationContainer, getFocusedRouteNameFromRoute } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const RootStack = createStackNavigator();
const Tab = createBottomTabNavigator();
function getHeaderTitle(route : any) {
// If the focused route is not found, we need to assume it's the initial screen
// This can happen during if there hasn't been any navigation inside the screen
// In our case, it's "Feed" as that's the first screen inside the navigator
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
console.log("name route = ", routeName);
switch (routeName) {
case 'Home':
return 'name header tab1';
case 'tab1':
return 'name header tab1';
case 'tab2':
return 'name header tab2';
}
}
const HomeTabs = () => {
return (
<Tab.Navigator>
<Tab.Screen name="tab1" component={Component1} />
<Tab.Screen name="tab2" component={Component2} />
</Tab.Navigator>
);
};
function Root() {
return (
<NavigationContainer>
<RootStack.Navigator>
<RootStack.Screen
name="Home"
component={HomeTabs}
options={({ route }) => ({
headerTitle: getHeaderTitle(route),
})}
/>
</RootStack.Navigator>
</NavigationContainer>
);
};
I am trying to create an app where I have a drawer and tabs navigators on each page.
I followed various tutorials and the react navigation docs but could not find a solution.
I have created a drawer navigator and within the <drawer.screen/> component I put my main tab navigator (My Tab navigator has multiple stacks - HomeStack, JoinStack, etc). So far so good however when I navigate to that main tab label in the menu, I return to the same screen I came from last and not to the top of my HomeStack although the "initialRouteName" is set to "Home".
I decided to leave it but remove the label, however it does not remove it completely. It removes the text but still there is a component being rendered there (see Image1 below)
Image1:
DrawerItem still being rendered
DrawerNavigator.js
//imports and stuff
const Drawer = createDrawerNavigator();
function MyDrawer({logout}) {
const nav = useNavigation()
return (
<Drawer.Navigator
initialRouteName={stackNavigationsConsts.HOME_STACK}
drawerPosition="right"
drawerContent={(props) => {
return (
<CustomDrawer nav={nav} drawerProps={props}/>
)
}}
>
<Drawer.Screen name={"בדיקה"} component={MainTabNavigator} options={{drawerLabel: () => null}}/>
<Drawer.Screen name="מאמר" component={Article} />
</Drawer.Navigator>
);
}
MainTabNavigator.js
//imports and stuff
const Tab = createBottomTabNavigator();
export default function MainTabNavigator() {
return (
<Tab.Navigator
initialRouteName={stackNavigationsConsts.HOME_STACK}
tabBarOptions={{
activeTintColor: mainColor.secondaryColor,
inactiveTintColor: mainColor.text,
activeBackgroundColor: mainColor.MainBackgroundColor,
// activeBackgroundColor: mainColor.buttonPress,
inactiveBackgroundColor: mainColor.MainBackgroundColor,
keyboardHidesTabBar: true,
}}
>
<Tab.Screen name={stackNavigationsConsts.HOME_STACK} component={HomeStackScreens}
options={{
tabBarLabel: navigationConsts.HOME,
tabBarIcon: ({ color, size }) => (
homeIcon(color)
),
}}
/>
<Tab.Screen name={stackNavigationsConsts.PROFILE_STACK} component={AnotherStack2Screen} options={{
tabBarLabel: navigationConsts.PROFILE ,
tabBarIcon: ({ color, size }) => (
profileIcon(color)
),
}}/>
<Tab.Screen name={stackNavigationsConsts.JOIN_STACK} component={JoinStackScreens}
options={{
tabBarLabel: navigationConsts.JOIN ,
tabBarIcon: ({ color, size }) => (
JoinIcon(color)
),
}}/>
<Tab.Screen name={stackNavigationsConsts.NOTIFICATIONS_STACK} component={AnotherStackScreen} options={{
tabBarLabel: navigationConsts.NOTIFICATIONS ,
tabBarIcon: ({ color, size }) => (
messagesIcon(color)
),
tabBarBadge: 12
}}/>
<Tab.Screen name={stackNavigationsConsts.ADD_RIDE_STACK} component={AnotherStack1Screen} options={{
tabBarLabel: navigationConsts.ADD_RIDE ,
tabBarIcon: ({ color, size }) => (
addRideIcon(color)
),
}}/>
</Tab.Navigator>
);
}
I found the solution here - How to hide Drawer Item from Drawer #2021 There is a workaround from v5 onward, need to extract the list of routes from the drawer navigation state and filter out the label you don't want to as I understood and it worked perfectly!
Please see below my render function for the custom drawer:
render() {
const {state, ...rest} = this.props.drawerProps
const newState = {...state}
newState.routes = newState.routes.filter((item) => item.name !== "זמני")
return (
<DrawerContentScrollView {...this.props}
>
<View style={styles.avatar}>
<View style={styles.imageContainer}>
<Image style={styles.image} source={require('../../../assets/images/man_smile.png')}/>
<Text style={{...styles.text, fontSize:22, marginTop:5}}>גלעד דהן</Text>
</View>
</View>
<View style={styles.items}>
<DrawerItemList
state={newState}
{...rest}
/>
<DrawerItem label={navigationConsts.MAIN} onPress={() => this.props.nav.navigate(stackNavigationsConsts.HOME_STACK)} labelStyle={styles.text}/>
<DrawerItem label="התנתק" onPress={() => this.props.logout()} labelStyle={styles.text}/>
</View>
</DrawerContentScrollView>
the icon shows in a screen/page, but won't show in the bottom navigation. Solutions that I've tried:
Follow the installation guide from github, I've tried both the GRADLE & MANUAL options, but same result
Have tried to ./gradlew clean then npx react-native run-android, but same result
Have tried to npx react-native link react-native-vector-icons then npx react-native run-android, but same result
screenshot bottom nav bar
screenshot setting screen
It does appear in screen/page as shown in above screenshot, but won't show in the bottom navigation.
NOTE: I've tested both in emulator and real android device, but still got same result!
Code for the bottom tab
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import Ionicons from 'react-native-vector-icons/Ionicons'
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs'
import ProductNavigation from './ProductNavigation'
import SettingScreen from '../screen/SettingScreen'
const BottomTab = createBottomTabNavigator();
const BottomTabNav = () => {
return (
<BottomTab.Navigator>
<BottomTab.Screen
name="Home"
component={ProductNavigation}
options={{
tabBarLabel: "Home",
tabBarIcon:({color, size}) => {
<Ionicons name="home-outline" color={color} size={size} />
}}} />
<BottomTab.Screen
name="Settings"
component={SettingScreen}
options={{
tabBarLabel: "Settings",
tabBarIcon: ({color, size}) => {
<Ionicons name="settings-outline" color={color} size={size}
/>
}}} />
</BottomTab.Navigator>
)
}
export default BottomTabNav
const styles = StyleSheet.create({})
Also can you help why does the bottom tab goes to the next page?? where should I edit the code, thanks in advance. Below is the Screenshot:
The issue is very simple actually, you are not returning anything from the function,
tabBarIcon: ({color, size}) => {
//nothing returned here
<Ionicons name="settings-outline" color={color} size={size}
/>
You have to do this, either use brackets like below or use the return statement to your code.
tabBarIcon: ({color, size}) => (
<Ionicons name="settings-outline" color={color} size={size}
/>)
First, make sure you use the icons correctly.
For example, suppose we use MaterialCommunityIcons.
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator
initialRouteName="Home"
tabBarOptions={{
activeTintColor: '#e91e63',
}}
>
<Tab.Screen
name="Home"
component={Home}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="home" color={color} size={size} />
),
}}
/>
<Tab.Screen
name="Settings"
component={Settings}
options={{
tabBarLabel: 'Updates',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="settings" color={color} size={size} />
),
tabBarBadge: 3,
}}
/>
Tab.Navigator>
);
}
General usage is like this. Check the document for details. https://reactnavigation.org/docs/bottom-tab-navigator/
In my case I hadn't added
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
to the android/app/build.gradle as indicated in the [Oblador React Native Vector Icons README document][1]
[1]: https://github.com/oblador/react-native-vector-icons#android
Once I added that, my icons displayed.
You need to create a custom tab bar component, and add the icons there. React Navigation has a pretty good documentation - https://reactnavigation.org/docs/bottom-tab-navigator#tabbar