React navigation drawer navigator with header back button - javascript

I'm using the drawer navigation from React Navigation v5. In my root file i've created the drawer navigator. Some of the screens inside of this navigator has a nested stack navigator. The first item is dashboard and the second item is Relations.
The problem is when I go to relations I don't get a back button for going to the first screen (Dashboard). Is it possible to add this to my relations screen?
Root code:
<NavigationContainer>
<DrawerNavigator.Navigator
drawerContent={(props) => (
<SidebarComponent {...props} user={this.props.device.user} />
)}
drawerPosition="right"
drawerStyle={{width: '90%', padding: 0, backgroundColor: 'red'}}>
{this.props.authenticated && this.props.device.api_key ? (
<>
<DrawerNavigator.Screen
name="Home"
options={{
headerShown: false,
icon: 'tachometer-alt',
category: 'dashboard',
}}
component={DashboardStack}
/>
<DrawerNavigator.Screen
name="Relations"
options={{
icon: 'address-book',
category: 'dashboard',
}}
component={RelationsStack}
/>
</>
) : (
<>
<DrawerNavigator.Screen
name="login"
options={{headerShown: false, gestureEnabled: false}}
component={LoginStack}
/>
</>
)}
</DrawerNavigator.Navigator>
</NavigationContainer>
Relation stack code:
import 'react-native-gesture-handler';
import React from 'react';
import {createStackNavigator} from '#react-navigation/stack';
import RelationsListScreen from '../RelationsListScreen';
import {colors} from '../../../assets/styles/variables';
const Stack = createStackNavigator();
function RelationsStack() {
return (
<Stack.Navigator>
<Stack.Screen
options={{
headerShown: true,
headerTintColor: '#FFF',
headerStyle: {
backgroundColor: colors.primary,
shadowColor: 'transparent',
},
}}
name="Relations"
component={RelationsListScreen}
/>
</Stack.Navigator>
);
}
export default RelationsStack;

You could create a stack navigator that is a screen of your drawer navigator (when the user is authenticated) which has Home and Relations as screens. I've called this navigator AuthenticatedNavigator in the example below:
const AuthenticatedStack = createStackNavigator();
// ...
const AuthenticatedNavigator = () => {
return (
<AuthenticatedStack.Navigator screenOptions={{headerShown: false}}>
<AuthenticatedStack.Screen
name="Home"
options={{
icon: 'tachometer-alt',
category: 'dashboard',
}}
component={DashboardStack}
/>
<AuthenticatedStack.Screen
name="Relations"
options={{
icon: 'address-book',
category: 'dashboard',
}}
component={RelationsStack}
/>
</AuthenticatedStack.Navigator>
);
};
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItem
label="Home"
onPress={() => props.navigation.navigate('Home')}
/>
<DrawerItem
label="Relations"
onPress={() => props.navigation.navigate('Relations')}
/>
</DrawerContentScrollView>
);
}
function App() {
const authenticated = true;
return (
<NavigationContainer>
<DrawerNavigator.Navigator
drawerPosition="right"
drawerStyle={{width: '90%', padding: 0, backgroundColor: 'red'}}
drawerContent={(props) => <CustomDrawerContent {...props} />}>
{authenticated ? (
<DrawerNavigator.Screen
name="authenticated"
component={AuthenticatedNavigator}
/>
) : (
<DrawerNavigator.Screen
name="login"
options={{headerShown: false, gestureEnabled: false}}
component={LoginStack}
/>
)}
</DrawerNavigator.Navigator>
</NavigationContainer>
);
}
I've also used a custom drawer content component so the links in the drawer still work correctly after using the approach of creating another stack navigator. You can read more about providing a custom drawer component in the documentation here: https://reactnavigation.org/docs/drawer-navigator/#providing-a-custom-drawercontent.
I've left out some code and made authenticated a hardcoded value to simplify the example. Also be sure to import DrawerItem, DrawerContentScrollView from #react-navigation/drawer.

Related

How to get the current route name of a Stack Navigator that is inside a Bottom Tab Navigator?

I have a Bottom Tab Navigation like this:
const tabs: {
name: keyof RootNavigationParams;
component: any;
}[] = [
{
name: "BalanceStackNavigator",
component: BalanceStackNavigator,
},
{
name: "BudgetScreen",
component: BudgetScreen,
},
{
name: "EntriesScreen",
component: EntriesScreen,
},
{
name: "SettingsScreen",
component: SettingsScreen,
},
];
return (
<Tab.Navigator
screenOptions={({ route }) => ({
header: (props) => <Header {...props} />,
tabBarIcon: ({ focused }) => renderIcons(focused, route),
tabBarStyle: {
borderTopWidth: 0,
elevation: 0,
backgroundColor: colors.background,
},
})}>
{tabs.map(({ name, component }) => (
<Tab.Screen
key={name}
name={name}
options={{ tabBarShowLabel: false }}
component={component}
/>
))}
</Tab.Navigator>
);
Inside this Bottom Tab Navigator I have the next Stack Navigator:
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="BalanceScreen" component={BalanceScreen} />
<Stack.Screen name="AddExpenseScreen" component={AddExpenseScreen} />
</Stack.Navigator>
Now, what I want to do is on the Header that is on the Bottom Tab Navigation, I want to change the header depending on which screen the app is.
const Header = ({ route }: BottomTabHeaderProps) => {
return (
<View>
<Text>
{route.name}
</Text>
</View>
);
};
The problem here is if I'm under some of the routes inside BalanceStackNavigator the route name is "BalanceStackNavigator", not the name of the current screen inside BalanceStackNavigator. Is there a way to get the current route name even if its inside a Stack Navigator?
If you want the name of screens from your nested BalanceStackNavigator you should pass Header to it:
<Stack.Navigator screenOptions={{ header: (props) => <Header {...props} /> }}>
<Stack.Screen name="BalanceScreen" component={BalanceScreen} />
<Stack.Screen name="AddExpenseScreen" component={AddExpenseScreen} />
</Stack.Navigator>
Then you could hide the header of the Tab when you are in BalanceStackNavigator, like so:
<Tab.Navigator
screenOptions={({ route }) => ({
header: (props) => <Header {...props} />,
tabBarIcon: ({ focused }) => renderIcons(focused, route),
tabBarStyle: {
borderTopWidth: 0,
elevation: 0,
backgroundColor: colors.background,
},
})}
>
{tabs.map(({ name, component }) => (
<Tab.Screen
key={name}
name={name}
options={{ tabBarShowLabel: false, headerShown: name !== "BalanceStackNavigator" }}
component={component}
/>
))}
</Tab.Navigator>;

How can I add an onPress event listener to a drawer navigation item in react native react navigation?

I have a drawer navigation configured in the following way:
const CustomDrawerContent = () => { return ( <DrawerItem label="Log out2" onPress={() => logOut()} /> ) }
const loginStack = () => (
<Stack.Navigator >
<Stack.Screen name='LandingScreen' component={LandingScreen} options={{headerShown: false}} />
<Stack.Screen name='LoginScreen' component={LoginScreen} options={{headerShown: false}} />
<Stack.Screen name='RegisterScreen' component={RegisterScreen} options={{headerShown: false}} />
</Stack.Navigator>
)
return (
<NavigationContainer>
<Drawer.Navigator
screenOptions={{
drawerStyle: { backgroundColor: 'white' },
drawerPosition: 'right'
}}>
{!user ? (
<Drawer.Screen
name="PublicStack"
component={loginStack}
options={{headerShown: false}}
/> )
:
(<>
<Drawer.Screen name='Search cocktails' component={HomeScreen} options={{ header: () => <Header/> }} />
<Drawer.Screen name='Profile' component={ProfileScreen} options={{ header: () => <Header/> }} />
<Drawer.Screen name='Publish a recipe' component={PublishRecipeScreen} options={{ header: () => <Header/> }} />
<Drawer.Screen name='Favorites' component={FavoritesScreen} options={{ header: () => <Header/> }} />
<Drawer.Screen name='Published recipes' component={PublishedRecipesScreen} options={{ header: () => <Header/> }} />
<Drawer.Screen name='Log out' component={CustomDrawerContent} options={{ header: () => <Header/> }} />
<Drawer.Screen name='CocktailDetailScreen' component={CocktailDetailScreen} options={{
header: () => <Header/>,
drawerLabel: () => null,
title: undefined
}} />
</>
)}
</Drawer.Navigator>
</NavigationContainer>
)
All screens work fine, but I want Log out to execute a logout function onPress. As I understand, I can't add this event listener directly on the screen component, so I followed this doc (https://reactnavigation.org/docs/drawer-navigator/#drawercontent) and tried a few different things:
I created the component CustomDrawerContent which is a DrawerItem.
If I pass CustomDrawerContent as the component to the Log out screen (as the code is right now), when I click on it I get redirected to a blank page that renders CustomDrawerContentcomponent, which isn't what I want.
If I pass CustomDrawerContent as drawerContent props to the drawer navigator, like the doc says (example below), all other screens dont render anymore, which is again not what I want.
<Drawer.Navigator drawerContent={(props) => <CustomDrawerContent />}>
{/* screens */}
</Drawer.Navigator>
If I put the drawer item together with the screens inside the navigator, the app throws the following error:
useNavigationBuilder.tsx:134 Uncaught Error: A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'DrawerItem'). To render this component in the navigator, pass it in the 'component' prop to 'Screen'.
So how can I add the item to the drawer without 'overwritting' the screens?
Or is there other way to put a simple logout button in the drawer?
Full code can be found here: https://github.com/coccagerman/mixr

React Native - How do I direct to Main Stack screen without logging in and redirect back to Auth Stack from Main Stack screen?

I'm building an app that consists of public and private route.
The private route is as below:
Sign in (starting screen) --> (user signed in) --> Main Stack Screens (Home, Country, Communities, etc.)
The public route is as below:
Sign in (starting screen) --> (user not signed in) --> Main Stack Screens (Home, Country, Communities, etc.) with limited functions and content display to the user
In public route, I'm trying to add a login icon in the header of the Main Stack Screens, so users can be redirected back to the Auth flow and log in or sign up at any point of time when they wish to.
Here is my code for AuthStackNavigator.js:
import React from "react";
import { createStackNavigator } from "#react-navigation/stack";
import SignInScreen from "../screen/authentication/SignInScreen";
import SignUpScreen from "../screen/authentication/SignUpScreen";
import SignUpSuccessScreen from "../screen/authentication/SignUpSuccessScreen";
import PasswordResetScreen from "../screen/authentication/PasswordResetScreen";
const Stack = createStackNavigator();
const AuthStackNavigator = () => {
return (
<Stack.Navigator
initialRouteName="Sign In"
screenOptions={{ headerShown: false }}
>
<Stack.Screen name="Sign In" component={SignInScreen} />
<Stack.Screen name="Sign Up" component={SignUpScreen} />
<Stack.Screen name="Sign Up Success" component={SignUpSuccessScreen} />
<Stack.Screen name="Password Reset" component={PasswordResetScreen} />}
</Stack.Navigator>
);
};
export default AuthStackNavigator;
Here for MainNavigator.js:
import React, { useContext } from "react";
import { Platform, StyleSheet } from "react-native";
import { createStackNavigator } from "#react-navigation/stack";
import CountryScreen from "../screen/CountryScreen";
import CommunitiesScreen from "../screen/CommunitiesScreen";
import CommunityCreateScreen from "../screen/authorized/CommunityCreateScreen";
import CommunityHomeScreen from "../screen/CommunityHomeScreen";
import PostCreateScreen from "../screen/authorized/PostCreateScreen";
import CommentPostScreen from "../screen/authorized/CommentPostScreen";
import SearchBar from "../component/SearchBar";
import { generalconfig } from "../helper/generalconfig";
import { stylesconfig } from "../helper/stylesconfig";
import { AuthContext } from "../src/context/AuthContext";
import CustomProfileImg from "../component/CustomProfileImg";
import DefaultProfileIcon from "../component/DefaultProfileIcon";
import { Entypo } from "react-native-vector-icons";
import LoginIcon from "../component/LoginIcon";
const MainNavigator = () => {
const Stack = createStackNavigator();
const platform = Platform.OS;
const { width } = generalconfig.device;
const headerImageContainer = stylesconfig.global.headerImageContainer;
const { common, ios, aos } = stylesconfig.navigatorHeader;
const { profileImg, token } = useContext(AuthContext);
return (
<Stack.Navigator>
<Stack.Screen
name="Search Country"
component={CountryScreen}
options={{
title: "Search",
headerStyle: {
backgroundColor: common.backgroundColor,
},
headerTitleAlign: "left",
headerTintColor: common.color,
headerTitleStyle: {
fontFamily: platform === "ios" ? ios.fontFamily : aos.fontFamily,
fontWeight: platform === "ios" ? ios.fontWeight : aos.fontWeight,
fontSize: platform === "ios" ? ios.fontSize : aos.fontSize,
},
headerRight: () =>
profileImg ? (
<CustomProfileImg
activeOpacity={1.0}
onPress={() => {}}
src={{ uri: profileImg }}
style={headerImageContainer}
/>
) : (
<DefaultProfileIcon
activeOpacity={1.0}
onPress={() => {}}
iconSize={55}
iconPosition={{ height: 65, marginRight: 11 }}
/>
),
}}
/>
<Stack.Screen
name="Communities"
component={CommunitiesScreen}
options={{
headerStyle: {
backgroundColor: common.backgroundColor,
},
headerTintColor: common.color,
headerTitleStyle: {
fontFamily: platform === "ios" ? ios.fontFamily : aos.fontFamily,
fontWeight: platform === "ios" ? ios.fontWeight : aos.fontWeight,
fontSize: platform === "ios" ? ios.fontSize : aos.fontSize,
},
headerBackTitle: " ",
headerBackImage: () => (
<Entypo
name="controller-fast-backward"
size={23}
style={{ marginLeft: width - 376 }}
color={common.color}
/>
),
headerRight: () => (token ? <LoginIcon /> : null),
}}
/>
<Stack.Screen
name="Create Community"
component={CommunityCreateScreen}
options={{
headerTitle: "Create a Community",
}}
/>
<Stack.Screen name="Community Home" component={CommunityHomeScreen} />
<Stack.Screen name="Create Post" component={PostCreateScreen} />
<Stack.Screen name="Comment Post" component={CommentPostScreen} />
</Stack.Navigator>
);
};
const styles = StyleSheet.create({});
export default MainNavigator;
LoginIcon.js (component)
import React from "react";
import { TouchableOpacity } from "react-native";
import { stylesconfig } from "../helper/stylesconfig";
import { MaterialCommunityIcons } from "react-native-vector-icons";
import AuthStackNavigator from "../navigator/AuthStackNavigator";
const LoginIcon = () => {
const { common } = stylesconfig.navigatorHeader;
return (
<TouchableOpacity
onPress={() => {
<NavigationContainer>
<AuthStackNavigator />
</NavigationContainer>;
}}
>
<MaterialCommunityIcons
name="login"
size={23}
color={common.color}
style={{ marginRight: 15 }}
/>
</TouchableOpacity>
);
};
export default LoginIcon;
SignInScreen.js (partial code of the link navigating to Country Screen when it's pressed)
<CustomButtonLink
custBtnLinkName={browseAroundLabel}
style={[styles.spacing_Browse, styles.align]}
onNavigate={() => {
navigation.navigate(MainNavigator, { screen: "Search Country" });
}}
onNavigate={() => <MainNavigator />}
/>
CustomButtonLink.js (component)
const CustomButtonLink = ({ custBtnLinkName, style, onNavigate }) => {
return (
<TouchableOpacity style={style} onPress={onNavigate}>
<Text
style={[
stylesconfig.global.gtext,
generalconfig.platform.os === "ios"
? stylesconfig.ios.text
: stylesconfig.aos.text,
]}
>
{custBtnLinkName}
</Text>
</TouchableOpacity>
);
};
So my questions are:
How do I direct the user to the public route (Country Screen) if user is not authenticated?
Once user is directed to the public route (say Country Screen), how do I redirect him back to the AuthStackNavigator (Sign In screen) when he clicks the login icon?
I'm quite new to React Native so deeply appreciate it if anyone can help. Thanks!
For Authentication Flows you should be using conditional operations such as "&&", the ternary operator, etc.
The Way recommended by react navigation is to use the above, you can follow their guide here.
E.G.
isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)
Apart from #Abhishek Sah answer, it's recommended to detect user authentication state and navigator rerender accordingly. This will help when user successfully registered or logged out.
// Custom hook to continuously listen to user authentication state
const isSignedIn = useAth()
// listen to authentication state and rerender navigation accordingly
useEffect(()=>{
},[isSignedIn])
isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)

Using navigation.openDrawer in Stack Navigator

I am trying to use the navigation prop inside my Stack Navigator to open the drawer when the material icon is clicked. However when I click th button I recieve the error:
Undefined is not an object (evaluating navigation.openDrawer)
I am confused as I have passed the navigation prop into the 'App' function. Where am I going wrong here?
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import {createDrawerNavigator} from '#react-navigation/drawer';
import HomeScreen from './src/screens/HomeScreen';
import SecondScreen from './src/screens/SecondScreen.js';
import {MaterialIcons} from '#expo/vector-icons';
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator();
const TheDrawer = () => {
return(
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="SecondScreen" component={SecondScreen} />
</Drawer.Navigator>
);
}
const App = ({navigation}) =>{
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"component={TheDrawer}
options={{headerTitle:
<View>
<MaterialIcons
name='menu'
onPress={() => navigation.openDrawer()} size={28}
/>
</View>
}}
/>
<Stack.Screen name="SecondScreen" component={SecondScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
The navigation prop only exist inside a Navigator, and different Navigators will have different navigation props (the Stack Navigator navigation will NOT have the openDrawer method, for example). I think that what you want to accomplish is this:
const App = () => {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen
name="Home"
component={HomeScreen}
options={({ navigation }) => ({
headerTitle: (
<View>
<MaterialIcons name="menu" onPress={() => navigation.openDrawer()} size={28}/>
</View>
),
})}
/>
<Drawer.Screen name="SecondScreen" component={SecondScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
};
Or, if you want the menu button available on every page header:
const App = () => {
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="Home"
screenOptions={({ navigation }) => ({
headerTitle: (
<View>
<MaterialIcons name="menu" onPress={() => navigation.openDrawer()} size={28} />
</View>
),
})}
>
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="SecondScreen" component={SecondScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
};
Source: https://reactnavigation.org/docs/headers/
If you need to access more pages inside this two (Home, SecondScreen), you can create it like this:
const HomeScreen = () => {
<Stack.Navigator>
<Stack.Screen name="Home1" component={HomeScreen1} />
<Stack.Screen name="Home2" component={HomeScreen2} />
<Stack.Screen name="Home3" component={HomeScreen3} />
</Stack.Navigator>
}
Source: https://reactnavigation.org/docs/nesting-navigators

React Navigation v5 How to hide tab from stack screen

I have an app that is composed of a tabbed navigation with 5 screens and one of them is a stack navigation of two screens. On one of the two stack screens I want to hide the bottom tabs. How can I do that ?
I already checked React Navigation V5 Hide Bottom Tabs but when I tried using navigation.setOptions({ tabBarVisible: false }) it changed the options for the stack navigator not the tab one.
Here is my code
// Screen where I want to hide the BottomTabNavigator
function StackSecondScreen({ navigation }) {
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "indianred"
}}
>
<Text>StackSecondScreen</Text>
</View>
);
}
const Stack = createStackNavigator();
function TabFirstScreen({ navigation }) {
return (
<Stack.Navigator initialRouteName="StackFirst">
<Stack.Screen
name="StackFirst"
component={StackFirstScreen}
options={{
headerShown: false
}}
/>
<Stack.Screen name="StackSecond" component={StackSecondScreen} /> // <== Screen where I want to hide the BottomTabNavigator
</Stack.Navigator>
);
}
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator initialRouteName="First">
<Tab.Screen name="TabFirst" component={TabFirstScreen} />
<Tab.Screen name="TabSecond" component={TabSecondScreen} />
<Tab.Screen name="TabThird" component={TabThirdScreen} />
<Tab.Screen name="TabFourth" component={TabFourthScreen} />
<Tab.Screen name="TabFifth" component={TabFifthScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}

Categories