React Navigation : Open drawer when I click on bottom tab navigator - react-native

With React Navigation 5, I want to open Drawer when I click on bottom tab navigator (I use material bottom navigator).
I manage to create the bottom tabs buttons and click on them, the home page opens for both tabs (GymIndexScreen or FoodIndexScreen).
When I am on the home pages (GymIndexScreen or FoodIndexScreen), I can open the different Drawers with my fingers (GymDrawerNavigator and FoodDrawerNavigator ) : everything works fine.
Question :
I want the drawers to open / close (toggle) automatically when I click the bottom tabs buttons, without having to open them with my fingers.
App.js :
import { NavigationContainer } from '#react-navigation/native'
const App = () => {
return (
<NavigationContainer>
<BottomTabNavigator />
</NavigationContainer>
)
}
BottomTabNavigator.js :
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs'
const Tab = createMaterialBottomTabNavigator()
const BottomTabNavigator = (props) => {
return (
<Tab.Navigator>
<Tab.Screen
name="Gym"
component={GymDrawerNavigator}
options={{
tabBarLabel: "Musculation",
)}
/>
<Tab.Screen
name="Food"
component={FoodDrawerNavigator}
options={{
tabBarLabel: "Alimentation",
)}
/>
</Tab.Navigator>
)
}
GymDrawerNavigator.js :
import { createDrawerNavigator } from '#react-navigation/drawer'
const Drawer = createDrawerNavigator()
const GymDrawerNavigator = () => {
return (
<Drawer.Navigator>
<Drawer.Screen
name="Gym"
component={GymStackNavigator}
/>
</Drawer.Navigator>
)
}
GymStackNavigator.js :
import { createStackNavigator } from '#react-navigation/stack'
const Stack = createStackNavigator()
const GymStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="GymIndex">
<Stack.Screen
name="GymIndex"
component={GymIndexScreen}
}}
/>
<Stack.Screen
name="GymExerciseIndex"
component={GymExerciseIndexScreen}
}}
/>
... list of screens

If I understood your problem correctly you want to open the drawer automatically when you navigate to the screen?
Add this to the screen components you wish to open the drawer when navigated to.
import {useEffect} from 'react'
...
useEffect(()=>{
navigation.addListener('focus', () => {
// when screen is focused (navigated to)
navigation.openDrawer();
});
},[navigation])``

This answer helped me.
Just use the listeners prop to preventDefault behaviour and then open the drawer.
<Tabs.Screen
name={"More"}
listeners={({ navigation }) => ({
tabPress: e => {
e.preventDefault();
navigation.openDrawer();
}
})}
component={Home}
/>

Related

how to use this bottom tab bar with react native?

I am using the react navigation package. how can i make this bottom tab bar?
I couldn't find how to do it with this bottom tab bar react navigation. Anyone know how to do it in react native?
You can try #react-navigation/bottom-tabs and tabBar props to custom BottomTab:
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const BottomTab = createBottomTabNavigator();
const BottomMenu = (props) => {
return (
// render your design
)
}
function MyTabs() {
return (
<BottomTab.Navigator tabBar={(props) => <BottomMenu {...props} />}>
<BottomTab.Screen name="Home" component={HomeScreen} />
<BottomTab.Screen name="Settings" component={SettingsScreen} />
</BottomTab.Navigator>
);
}

How to hide bottom bar on modal screen?

I have an app with a bottom bar and I'd like to present a modal screen from one of them without the bottom tab. I don't understand why it works as expected on iOS but not on Android.
Here is my bottom tab navigator which contains all my tabs:
// AppStack.tsx
const Tab = createBottomTabNavigator()
const AppStack = () => {
return (
<Tab.Navigator initialRouteName="homeStack" >
<Tab.Screen name="homeStack" component={HomeStack} />
...
</Tab.Navigator>
)
}
And here is the stack navigator that contains several screens but also contains one screen that should be presented modally (thus without the bottom bar).
// HomeStack.tsx
const Stack = createNativeStackNavigator()
const HomeStack = () => {
return (
<Stack.Navigator initialRouteName="home">
<Stack.Screen name="home" component={HomeScreen} />
...
<Stack.Screen
name="addStuff"
component={StuffStack}
options={{
presentation: 'fullScreenModal',
}}
/>
</Stack.Navigator>
)
}
Presenting the addStuff modal screen from one of HomeStack's screens works on iOS as expected: the bottom bar isn't displayed. But on Android, the bottom bar is still present...
// HomeScreen.tsx
navigation.navigate('addStuff')
Does anybody have an idea on how to tell react-navigation not to add this bottom bar on this modal screen?
You can create conditional rules to check the screen name.
To achieve this, you need to get the navigation props (which is present on your HomeStack) and use the getFocusedRouteNameFromRoute method to get the current screen name:
import {getFocusedRouteNameFromRoute} from '#react-navigation/native';
// rest of the codes ...
const HomeStack = ({route, navigation}) => {
React.useLayoutEffect(() => {
const routeName = getFocusedRouteNameFromRoute(route);
if (routeName === 'addStuff') {
navigation.setOptions({tabBarVisible: false});
} else {
navigation.setOptions({tabBarVisible: true});
}
}, [navigation, route]);
return (
<Stack.Navigator initialRouteName="home">
<Stack.Screen name="home" component={HomeScreen} />
// rest of the codes ...
<Stack.Screen
name="addStuff"
component={StuffStack}
options={{
presentation: 'fullScreenModal',
}}
/>
</Stack.Navigator>
)
}

React navigation v5 slow because rerenders current screen before opening drawer

I'm working on a bigger app and have some lag when opening the drawer. It takes about 1 second for the drawer animation to begin.
I looked into it with react profiler and saw that the drawer is rendered and the current screen is rerendered before the drawer opens. This makes things feeling slow, I would not suspect the current screen to rerender.
Here is how my pseudo navigation stack looks topdown:
//Toplevel
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const RootStack = createStackNavigator();
<NavigationContainer ref={navRef} theme={navTheme} linking={linking}>
<RootStack.Navigator>
{loggedIn ? (
<RootStack.Screen component={inBetweenComponent} /> // => calls drawer
) : (
// auth screen
)}
</RootStack.Navigator>
</NavigationContainer>
const DrawerNavigator = () => {
const Drawer = createDrawerNavigator();
const route = useRoute();
const activeRoute = getFocusedRouteNameFromRoute(route);
return (
<Drawer.Navigator
drawerContent={props => <DrawerContent {...props} activeRoute={activeRoute} />}
>
<Drawer.Screen name="Notifications" component={NotificationsNavigator} />
<Drawer.Screen name="Conversations" component={ConversationsNavigator} />
</Drawer.Navigator>
}
const DrawerContent = ({ activeRoute }) => {
const navigation = useNavigation();
return (
<DrawerContentScrollView>
<TouchableNative onPress={goToNotifications} >..some text...</TouchableNative>
<TouchableNative onPress={goToConversations} >..some text...</TouchableNative>
</DrawerContentScrollView>
)
}
const NotificationsNavigator = ({ navigation }) => {
const Stack = createStackNavigator();
return (
<Stack.Navigator
screenOptions={{
headerLeft: () => <HeaderLeft navigation={navigation} />, // -> contains open drawer button
}}
initialRouteName="Notifications"
>
<Stack.Screen
name="Notifications"
component={NotificationsScreen}
/>
</Stack.Navigator>
);
}
const HeaderLeft = () => {
const navigation = useNavigation();
const openNavigation = () => {
navigation.openDrawer();
};
<TouchableNative /*someicon*/ onPress={openNavigator} />
I would like to know:
Is it normal that the current active screen rerenders before the drawer opens?
If normal, Is there any way around rerendering the current active screen? I tried a solution with useMemo + areEqual function with isDrawerOpen (useIsDrawerOpen) send from the parent. But that is not consistent and often gives wrong or undefined values.
Any other pointers to why this stack might be slow are greatly appreciated.
Thanks in advance!
The reason your screen is re-rendering when you open the drawer is because you're defining the drawer in the DrawerNavigator component.
This means every time React Navigation wants to find the drawer is has to reload not only the current screen, but every screen in the drawer.
Moving const Drawer = createDrawerNavigator(); outside of DrawerNavigator should solve your problem.

Reset stack navigation when tab button is pressed in ReactNav 5

I have 3 tabs and behind each tab is a stack navigation. I always want to reset the stack navigation when I click on another tab button.
Right now, when I go in Stack1 like A -> B -> C -> D
and I change to Tab2 and then change back to Tab1, I am again at Screen D.
I want to see Screen A again. I use React-Navigation-5
I would accept any answer that shows me a piece of code how to implement that.
My code looks like this:
App.js:
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Tab1" component={Stack1} />
<Tab.Screen name="Tab2" component={Stack2} />
<Tab.Screen name="Tab3" component={Stack3} />
</Tab.Navigator>
</NavigationContainer>
);
}
where as each of my stack navigations look like this:
function EventExploreStack({ navigation }) {
return (
<SettingsStack.Navigator initialRouteName="A">
<SettingsStack.Screen name="A" component={AScreen} />
<SettingsStack.Screen name="B" component={BScreen} />
<SettingsStack.Screen name="C" component={CScreen} />
<SettingsStack.Screen name="D" component={DScreen} />
<SettingsStack.Screen name="E" component={EScreen} />
</SettingsStack.Navigator>
);
}
export default EventExploreStack;
I am using React Navigation 5.
One option would be to use the reset action of the navigation. As you are having three stacks in three tabs you will need a custom tabbarbutton to do this which will reset the state of the given tab. The code for the button will be as below.
Here I've used Home and Settings as tabs, you will have to change them to your need.
const CustomButton = (props) => {
const navigation = useNavigation();
return (
<TouchableOpacity
{...props}
onPress={() => {
navigation.dispatch((state) => {
const newState = Object.assign(state);
const index = newState.routes.findIndex((x) => (x.name = 'Home'));
if (newState.routes[index].state) {
const { name, key } = newState.routes[index];
newState.routes[index] = { name, key };
}
return CommonActions.reset({
...newState,
});
});
navigation.navigate('Settings');
}}
/>
);
};
Which you can place in the tab screen as below
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarButton: (props) => <CustomButton {...props} />,
}}
/>
You can tryout the sample here
https://snack.expo.io/#guruparan/bottomnavclick
Hope this helps :)
Take a look at the **createBottomTabNavigator** here https://reactnavigation.org/docs/bottom-tab-navigator/
Example
npm install #react-navigation/bottom-tabs
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}
Create a helper that reset the stacks of all Tabs that are not currently selected, and pass it to every tab.
Like this:
Helper:
import { StackActions } from '#react-navigation/native';
export const resetStacksOnTabClicks = ({ navigation }: any) => ({
tabPress: (e: any) => {
const state = navigation.dangerouslyGetState();
if (state) {
const nonTargetTabs = state.routes.filter((r: any) => r.key !== e.target);
nonTargetTabs.forEach((tab: any) => {
if (tab?.state?.key) {
navigation.dispatch({
...StackActions.popToTop(),
target: tab?.state?.key,
});
}
});
}
},
});
Then pass that helper to every Tab in the listeners prop like this:
<Tabs.Screen
name="TabName"
component={YourComponent}
listeners={resetStacksOnTabClicks}
/>

React Native drawer navigation as part of tab navigation

I'm trying to implement a drawer navigation alongside a tab navigation, but I can't seem to wrap my head around how that would be achieved.
The tab would have 5 items, and should be present on all screens. The 5th tab should open a drawer with more menu items.
Clicking on any of the drawer's menu items should of course show those specific screens, but the tab navigation should be still be present.
In the code below the bottom tab bar (which could be replaced by a top tab navigator) is always present.
The last tab contains the the drawer. It doesn't open by default, but this can be achieved by a this.props.navigation.openDrawer() request.
import React from 'react';
import { View, Text } from 'react-native';
import { createBottomTabNavigator, createDrawerNavigator } from 'react-navigation';
const buildScreen = name => {
class Screen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>{name}</Text>
</View>
);
}
}
return Screen;
}
const DrawerScreen = createDrawerNavigator(
{
Child1: buildScreen("DrawerChild 1"),
Child2: buildScreen("DrawerChild 2"),
},
{
// optional drawer options
// cfr. https://reactnavigation.org/docs/en/drawer-based-navigation.html
}
);
export default createBottomTabNavigator({
Tab1: buildScreen("Tab1"),
Tab2: buildScreen("Tab2"),
Tab3: buildScreen("Tab3"),
Tab4: buildScreen("Tab4"),
DrawerScreen,
});
I know this is an old question, but for people still looking for an answer, this is how I implemented mine.
The Drawer Navigation has to be your main navigation. Your tab navigation is nested inside it.
import React from 'react';
import { createDrawerNavigator } from '#react-navigation/drawer';
import AppNavigator from './AppNavigator';
const Drawer = createDrawerNavigator();
function DrawerNavigation() {
return (
<Drawer.Navigator>
<Drawer.Screen name='Home' component={TabNavigation} />
</Drawer.Navigator>
);
}
export default DrawerNavigation;
In your Tab Navigation, the tab which you choose to show your Drawer Navigation (in your case the fifth tab) would have a listener prop which fires a function on tabPress, toggling the drawer navigation
import React from 'react';
import { Text } from 'react-native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
const Home = () => (
<Text> Hello Home</Text>
);
const Menu = () => {
return null;
};
function TabNavigation(props) {
return (
<Tab.Navigator>
<Tab.Screen
name='Home'
component={Home}
/>
<Tab.Screen
name='Menu'
component={Menu}
listeners={({ navigation }) => ({
tabPress: e => {
e.preventDefault();
navigation.toggleDrawer();
},
})}
/>
</Tab.Navigator>
);
}
export default TabNavigation;
You can create a stackNavigator with two screens and a materialTabNavigator inside:
const SomeStack= createStackNavigator({
Start: StartStack,
HomeScreenTab,
}
and then:
HomeScreenTab: {
screen: HomeScreenTabTab
}
with:
export default createMaterialTopTabNavigator(
{
Home: {
screen: HomeStack,
navigationOptions: {
tabBarAccessibilityLabel: 'Tela Inicial do APP',
tabBarLabel: ({ tintColor }) => <LabelTitle tintColor={tintColor}
label="Start" />,
tabBarIcon: ({ tintColor }) => (
<View style={styles.containerIcon}>
<FontAwesome color={tintColor} name="home" size{icons.iconMd}
light />
</View>
),
},
},
...SomeOtherTabs
}
You put the content of your screens on the screen property and so on.