Opening the Drawer navigator from the bottom tab - react-native

I'm upgrading my App from react-navigation 4 to 5.
In version 4 I have a tab that open the drawer using the following code
const MainNavigator = createBottomTabNavigator(
{
More: {
screen: AdminNavigator,
navigationOptions: {
tabBarIcon: (tabInfo) => {
//return <Ionicons name="ios-star" size={25} color={tabInfo.tintColor} />;
return (
<Icon
name="tune"
color={tabInfo.tintColor}
size={tabInfo.focused ? 32 : 28}
style={{
paddingTop: 10,
}}
/>
);
},
tabBarColor: Colors.primary,
tabBarOnPress: ({ navigation }) => {
navigation.openDrawer();
},
},
},
}
In new version 5 I have the following Navigation config
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={TabsScreen} />
<Drawer.Screen name="Favorites" component={FavoritesStackScreen} />
<Drawer.Screen name="Language" component={LanguageStackScreen} />
</Drawer.Navigator>
</NavigationContainer>
where TabScreen is my bottom tab navigator
const TabsScreen = () => (
<Tabs.Navigator>
<Tabs.Screen name={"Home"} component={HomeStackScreen} />
<Tabs.Screen name={"Cocktails"} component={CocktailsStackScreen} />
<Tabs.Screen
name={"More"}
component={HomeStackScreen}
options={{
...
}}
/>
</Tabs.Navigator>
);
I'm looking for the equivalent in V5 of the following
tabBarOnPress: ({ navigation }) => {
navigation.openDrawer();
},

Try the following:
<Tabs.Screen
name={"More"}
component={HomeStackScreen}
listeners={({ navigation }) => ({
tabPress: e => {
e.preventDefault();
navigation.openDrawer();
}
})}
/>
https://reactnavigation.org/docs/navigation-events/#listeners-prop-on-screen

Related

React Native Drawer Navigation show headerLeft

I have a Drawer navigator which is inside a Stack navigator and I'd like to display a header. Currently I can display everything I want however because the header is defined at the Stack level the navigation inside the header is stack level not drawer level which is preventing me from opening the drawer.
Root stack
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ auth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
headerRight: () => (
<HeaderRight navigation={ navigation } />
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
Drawer stack
<Drawer.Navigator options={{
headerShown: true,
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
}}>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ChatListScreen}
/>
<Drawer.Screen
name="ChatRoom"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null
}}
component={ChatRoomScreen}
/>
</Drawer.Navigator>
Note in the drawer navigator the line with headerLeft does nothing and is there to show where I attempted to put it thinking it would work. I did think it might be overlaying the stack one so I commented out the stack one and it didn't work.
HeaderLeft
export default function HeaderLeft ({ navigation }) {
const openMenu = () => {
navigation.toggleDrawer();
}
return (
<View style={styles.header}>
<Icon name='menu' onPress={openMenu} size={28} style={styles.icon} color="white"/>
</View>
)
}
My question is how can I refactor this to enable me to have the HeaderLeft component work to open the drawer. I will be adding more screens so ideally something I don't have to pass to each screen but if that is what works I am good with it too.
Options in DrawerStack not work. I modified it:
<Drawer.Navigator
screenOptions={{
headerLeft: () => <HeaderLeft />,
}}>
// ...
</Drawer.Navigator>
And a little change in HeaderLeft:
import { useNavigation } from '#react-navigation/native';
function HeaderLeft() {
const navigation = useNavigation();
const openMenu = () => {
navigation.toggleDrawer();
};
// render your Button
}
Demo: https://snack.expo.dev/#pqv2210/0d613b

DrawerNavigator to show tab bar all the time with every option tapped

I am trying to have a tab bar and each tab bar has its own stack navigator. Three tabs that i have are
Home
Profile
Settings
I want to show these three options in the drawer also. I have created a drawer but only tapping home shows the tab bar. I want to show tab bar just like if you press of profile tab and tab bar remains there.
Here is my code:
const HomeStackNavigator = createStackNavigator();
export const HomeNavigator = () => {
return (
<HomeStackNavigator.Navigator screenOptions={defaultNavOptions}>
<HomeStackNavigator.Screen
name="Home"
component={HomeScreen}
options={homeScreenOptions}
/>
<HomeStackNavigator.Screen
name="Details"
component={DetailsScreen}
options={detailsScreenOptions}
/>
</HomeStackNavigator.Navigator>
);
};
const ProfileStackNavigator = createStackNavigator();
export const ProfileNavigator = () => {
return (
<ProfileStackNavigator.Navigator screenOptions={defaultNavOptions}>
<ProfileStackNavigator.Screen
name="Profile"
component={ProfileScreen}
options={profileScreenOptions}
/>
<ProfileStackNavigator.Screen
name="EditProfile"
component={EditProfileScreen}
options={editProfileScreenOptions}
/>
</ProfileStackNavigator.Navigator>
);
};
const SettingsStackNavigator = createStackNavigator();
export const SettingsNavigator = () => {
return (
<SettingsStackNavigator.Navigator screenOptions={defaultNavOptions}>
<SettingsStackNavigator.Screen
name="Settings"
component={SettingsScreen}
options={settingsScreenOptions}
/>
<SettingsStackNavigator.Screen
name="AccountDetail"
component={AccountDetailsScreen}
options={accountDetailsScreenOptions}
/>
</SettingsStackNavigator.Navigator>
);
};
const HomeTabNavigator = createBottomTabNavigator();
export const TabNavigator = () => {
return (
<HomeTabNavigator.Navigator screenOptions={defaultNavOptions}>
<HomeTabNavigator.Screen
name="Home"
component={HomeNavigator}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="home" color={color} size={size} />
),
}}
/>
<HomeTabNavigator.Screen
name="Profile"
component={ProfileNavigator}
options={{
tabBarLabel: 'Profile',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="face-profile" color={color} size={size} />
),
}}
/>
<HomeTabNavigator.Screen
name="Settings"
component={SettingsNavigator}
options={{
tabBarLabel: 'Settings',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="account-settings" color={color} size={size} />
),
}}
/>
</HomeTabNavigator.Navigator>
);
};
const AppDrawer = createDrawerNavigator();
export const Drawer = () => {
return(
<AppDrawer.Navigator initialRouteName="Home">
<AppDrawer.Screen name="Home" component={TabNavigator} />
<AppDrawer.Screen name="Profile" component={ProfileNavigator} />
<AppDrawer.Screen name="Settings" component={SettingsNavigator} />
</AppDrawer.Navigator>
)
};
My goal is to have tabs all the time. Tabs should hide only if we go to the detail page of any of the tabs.
You can make your TabNavigator a screen of a stack navigator which is a screen of your drawer navigator and pass a custom drawer component to your drawer navigator:
const AppDrawer = createDrawerNavigator();
const Tab = createBottomTabNavigator();
const Stack = createStackNavigator();
const HomeTabNavigator = createBottomTabNavigator();
export const TabNavigator = () => {
return (
<HomeTabNavigator.Navigator>
<HomeTabNavigator.Screen
name="Home"
component={HomeScreen}
options={{
tabBarLabel: 'Home',
}}
/>
<HomeTabNavigator.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: 'Profile',
}}
/>
<HomeTabNavigator.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarLabel: 'Settings',
}}
/>
</HomeTabNavigator.Navigator>
);
};
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItem
label="Home"
onPress={() => props.navigation.navigate('Home')}
/>
<DrawerItem
label="Profile"
onPress={() => props.navigation.navigate('Profile')}
/>
<DrawerItem
label="Settings"
onPress={() => props.navigation.navigate('Settings')}
/>
</DrawerContentScrollView>
);
}
function getHeaderTitle(route) {
return getFocusedRouteNameFromRoute(route) ?? 'Home';
}
const StackNavigator = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="Tabs"
component={TabNavigator}
options={({route}) => ({
headerTitle: getHeaderTitle(route),
})}
/>
<Stack.Screen name="EditProfile" component={EditProfileScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
<Stack.Screen name="AccountDetail" component={AccountDetailsScreen} />
</Stack.Navigator>
);
};
export const Drawer = () => {
return (
<AppDrawer.Navigator
initialRouteName="Home"
drawerContent={(props) => <CustomDrawerContent {...props} />}>
<AppDrawer.Screen name="Stack" component={StackNavigator} />
</AppDrawer.Navigator>
);
};
function App() {
return (
<NavigationContainer>
<Drawer />
</NavigationContainer>
);
}
The screens you don't want to show the tabs for can be put inside the stack navigator outside of the tab navigator.
Sources:
https://reactnavigation.org/docs/drawer-navigator/#providing-a-custom-drawercontent.
https://reactnavigation.org/docs/screen-options-resolution/#setting-parent-screen-options-based-on-child-navigators-state.
Be sure to import DrawerItem, DrawerContentScrollView from #react-navigation/drawer and getFocusedRouteNameFromRoute from #react-navigation/native.

Dynamic items in Drawer nested in Stack Navigator react-navigation

i`m realy stumped about this.
I have a bottom tabs width 4 screens
BottomTabs.js
export const AllTabs = ({ navigation }) => {
return (
<Tab.Navigator initialRouteName="Home" tabBar={props => <BottomNavigation {...props} />}>
<Tab.Screen name="Home" component={HomeStack} options={{
icon: 'shopping-store'
}}/>
<Tab.Screen name="Catalog" component={CatalogStack} options={{
icon: 'pulse'
}}/>
<Tab.Screen name="Cart" component={CartStack} options={{
icon: 'shopping-basket'
}} />
<Tab.Screen name="Profile" component={ProfileStack} options={{
icon: 'person'
}}/>
</Tab.Navigator>
)
}
In first Screen named Home, in HomeStack:
export const HomeStack = () => {
return (
<Stack.Navigator screenOptions={screenOptions} >
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="SectionDrawer" component={SectionDrawer} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
)
}
I calling drawer component in second screen
Drawer
export const SectionDrawer = (props) => {
const { route, navigation } = props
const { list } = route.params
return (
<Drawer.Navigator drawerContentOptions={{ activeBackgroundColor: '#5cbbff', activeTintColor: '#ffffff' }} >
<Drawer.Screen name="Section" component={SectionScreen} options={{ route, navigation }}/>
</Drawer.Navigator>
)
}
And finally in SectionScreen i`m fetcing data with api by id
SectionScreen
export const SectionScreen = (props) => {
console.log(props);
const {route, navigation} = props
const { sectionID } = route.params
const [isLoading, setLoading] = useState(true);
const [productList, setProductList] = useState([]);
useEffect(() => {
fetch('url, {
method: 'GET'
})
.then((response) => response.json())
.then((json) => setProductList(json))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
return (
<Content padder>
<Text>section screen.</Text>
<Text>Requested id {sectionID}</Text>
{isLoading ? (
<Spinner />
) : (
<ProductCards list={productList} perLine={2} navigation={navigation}/>
)}
</Content>
)
}
I calling SectionScreen in drawer with this construction:
<CardItem cardBody button onPress={() => {
navigation.dispatch(
CommonActions.navigate({
name: 'SectionDrawer',
params: {
screen: 'SectionScreen',
params: {
title: card.title,
sectionID: card.id
}
},
})
);
}}>
All of this works ... but I need to push in drawer dynamic links fetched from api
I am tried: in Drawer
<Drawer.Navigator drawerContentOptions={{ activeBackgroundColor: '#5cbbff', activeTintColor: '#ffffff' }} >
{list.map((item, index) => <Drawer.Screen name={item.title} component={SectionScreen} key={index}/>)}
</Drawer.Navigator>
and in place where I calling SectionScreen:
<CardItem cardBody button onPress={() => {
navigation.dispatch(
CommonActions.navigate({
name: 'SectionDrawer',
params: {
list,
screen: card.title,
params: {
title: card.title,
sectionID: card.id
}
},
})
);
}}>
After this I see items that I passed in list param,
but when i try to navigate to them, i have error in SectionScreen route.param.id is not defined, and route.params is undefined too.
Also i trying to fetch data that I need in drawer, in drawer component, and draw it with
drawerContent={props => <CustomDrawerContent {...props} />}
and render them with:
{catalogSections.map((item, index) => <DrawerItem label={item.title} focused onPress={() => drawerNavigate(navigation, item)} key={index}/>)}
and it works fine but i cannot select active item from them...
help me please how right to add items to drawer and navigate always to one component with different params

React Navigation 5.x Drawer Issue

Getting the following Issue when I use Drawer. Stack and Tab are working fine without Drawer.
Does anyone know what is going on here and can help me?
enter image description here
Here is Navigator File some Code lines:
const Stack = createStackNavigator();
const FavStack = createStackNavigator();
const FilterStack = createStackNavigator();
const Tab =
Platform.OS === "android"
? createMaterialBottomTabNavigator()
: createBottomTabNavigator();
const defaultScreenOptions = {
headerStyle: { backgroundColor: colors.primary },
headerTintColor: "white",
};
function MealsNavigator() {
return (
<Stack.Navigator screenOptions={defaultScreenOptions}>
<Stack.Screen name="Meals Categories" component={CategoriesScreen} />
<Stack.Screen name="Profile" component={CategoryMealScreen} />
<Stack.Screen name="Details" component={MealDetailsScreen} />
</Stack.Navigator>
);
}
function FavStackNavigator() {
return (
<FavStack.Navigator screenOptions={defaultScreenOptions}>
<FavStack.Screen name="Favourites" component={FavouritesScreen} />
<FavStack.Screen name="Details" component={MealDetailsScreen} />
</FavStack.Navigator>
);
}
function MealsFavTabNavigator() {
return (
<Tab.Navigator
tabBarOptions={{ activeTintColor: colors.accent }}
shifting={true}
>
<Tab.Screen
name="All"
component={MealsNavigator}
options={{
tabBarLabel: "Meals",
tabBarIcon: (tabInfo) => (
<Ionicons name="ios-restaurant" size={25} color={tabInfo.color} />
),
tabBarColor: colors.primary,
}}
/>
<Tab.Screen
name="Favourites"
component={FavStackNavigator}
options={{
tabBarIcon: (tabInfo) => (
<Ionicons name="ios-star" size={25} color={tabInfo.color} />
),
tabBarColor: colors.accent,
}}
/>
</Tab.Navigator>
);
}
function FilterStackNavigator() {
return (
<FilterStack.Navigator>
<FilterStack.Screen name="Filters" component={FilterScreen} />
</FilterStack.Navigator>
);
}
const Drawer = createDrawerNavigator();
function MenuNavigator() {
return (
<Drawer.Navigator>
<Drawer.Screen name="MealFavs" component={MealsFavTabNavigator} />
<Drawer.Screen name="Filters" component={FilterStackNavigator} />
</Drawer.Navigator>
);
}
export default MenuNavigator;
Here is App.js few Code lines:
export default function App() {
const [fontLoaded, setFontLoaded] = useState(false);
if (!fontLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => setFontLoaded(true)}
onError={(err) => console.log(err)}
/>
);
}
return <NavigationContainer><MealsNavigator /></NavigationContainer>
}
Code is not giving error without Drawer, It is working exactly fine with Stack and Tab Navigators!

How to call toggleDrawer in React Navigation 5

I got a React-Navigation 5 drawer menu working using gesture, but I also want to add an icon on the right side of the header to toggle the drawer menu.
I have the navigation setup in my App.js like this:
import {NavigationContainer, DrawerActions} from '#react-navigation/native';
//....... other imports
const HomeStack = createStackNavigator();
const HomeStackScreens = () => (
<HomeStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#5C6BC0',
},
headerTintColor: '#fff',
headerRight: () => {
return (
<TouchableOpacity
onPress={() => DrawerActions.toggleDrawer()}>
<Icon name="bars" color="#FFF" size={18} />
</TouchableOpacity>
);
},
}}>
<HomeStack.Screen
name="Home"
component={HomeScreen}
options={{
header:({scene, previous, navigation}) => {
return (
<TouchableOpacity onPress={() => navigation.toggleDrawer()}>
<Icon name="bars" color="#FFF" size={18} />
</TouchableOpacity>
);
}
}}
/>
<HomeStack.Screen name="Login" component={Login} />
<HomeStack.Screen name="Register" component={Register} />
</HomeStack.Navigator>
);
const ProfileStack = createStackNavigator();
const ProfileStackScreens = () => (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Profile" component={Profile} />
</ProfileStack.Navigator>
);
const SettingStack = createStackNavigator();
const SettingStackScreens = () => (
<SettingStack.Navigator>
<SettingStack.Screen name="Profile" component={Profile} />
</SettingStack.Navigator>
);
const Drawer = createDrawerNavigator();
const DrawerScreens = () => (
<Drawer.Navigator>
<Drawer.Screen name="Home" component={HomeStackScreens} />
<Drawer.Screen name="Profile" component={ProfileStackScreens} />
<Drawer.Screen name="Settings" component={SetttingStackScreens} />
</Drawer.Navigator>
);
class MyApp extends React.Component {
render() {
return (
<NavigationContainer>
<DrawerScreens />
</NavigationContainer>
);
}
}
export default MyApp;
All my others screen are in the form "export default class ScreenName extends React.Component". They are imported in App.js to setup the navigation. The initial screen is Home. The icon is showing correctly on the right side of the header. Calling "DrawerActions.toggleDrawer()" directly does nothing.
I tried "this.props.navigation.toggleDrawer()", and it throws error because "this.props" is undefined.
How can I invoke toggleDrawer() with such a navigation setup? Any help is really appreciated!
Here is the final solution I come up with that requires minimal changes to my original code. The key is to receive "navigation" in screenOptions or options, then I can call navigation methods in the children of screenOptions/options.
<HomeStack.Navigator
screenOptions={({navigation}) => ({
headerStyle: {
backgroundColor: '#5C6BC0',
},
headerTintColor: '#fff',
headerRight: () => {
return (
<TouchableOpacity
style={{paddingRight: 8}}
onPress={() => navigation.toggleDrawer()}>
<Icon name="bars" color="#FFF" size={18} />
</TouchableOpacity>
);
},
})}>