Combining Stack, Drawer and Tab Navigator - javascript

I am having trouble realising the following:
I want a Tab Navigation with two Tabs: Home and Map.
In the Home Tab I want a Drawer Navigation. Within the Drawer Navigation I want to be able to Navigate to the screens Profile and About. When clicking the Back Button from Profile or About, I want to come back to the open Drawer Menu.
Currently I have this:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { createStackNavigator } from '#react-navigation/stack';
import DrawerContent from './DrawerContent';
import Mapp from './Mapp';
import Home from './Home';
import Profile from './Profile';
import About from './About';
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const Tab = createBottomTabNavigator();
const HomeStackNavigator = () => {
return <Stack.Navigator>
<Stack.Screen
name='Tab'
component={TabNavigator} />
<Stack.Screen
name='Profile'
component={Profile} />
<Stack.Screen
name='About'
component={About} />
</Stack.Navigator>
}
const TabNavigator = () => {
return <Tab.Navigator>
<Tab.Screen name='Home' component={Home} />
<Tab.Screen name='Map' component={Mapp} />
</Tab.Navigator>
}
const DrawerNavigator = () => {
return <Drawer.Navigator
drawerContent={props => DrawerContent(props)}>
<Drawer.Screen name='Home' component={HomeStackNavigator} />
</Drawer.Navigator>
}
const Navigation = () => {
return <NavigationContainer>
<DrawerNavigator />
</NavigationContainer >
}
and <DrawerContent /> looks like this
import React from 'react';
import {
DrawerContentScrollView,
DrawerItemList,
DrawerItem,
} from '#react-navigation/drawer';
const DrawerContent = props => {
return <DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem label='Profile' onPress={() => props.navigation.navigate('Profile')} />
<DrawerItem label='About' onPress={() => props.navigation.navigate('About')} />
</DrawerContentScrollView>
}
export default DrawerContent;
This gives me
The Tab Navigation (why is there a header with title Tab, I don't want this, but I must set name prop in <HomeStackNavigator />).
Then I can open the Drawer (works as expected)
When I click on i.e. Profile I am only able to go back to Tab. Why not to Drawer?
What also drives me nuts is, that I cannot remove the Home Menu Item in the Drawer.

The drawer doesn't open in that scenario, you will need to open it yourself:
const HomeStackNavigator = () => {
return <Stack.Navigator>
<Stack.Screen
name='Tab'
component={TabNavigator} />
<Stack.Screen
name='Profile'
component={Profile}
options={({ navigation }) => ({
title: '',
headerLeft: () => (
<Button
title="<"
onPress={() => {
navigation.goBack();
navigation.openDrawer();
}}
/>
),
})}/>
<Stack.Screen
name='About'
component={About} />
</Stack.Navigator>
}
Here's a working Snack.

Related

How to remove default styles from react-navigation?

I was making an app with react native and react-navigation. So what I did was I made a Login screen. Then I used react-navigation to create a native stack navigator and linked it to my Login screen. I successfully rendered the Login Screen but there seems to be some sort of default styling on the stack navigator(?). How do I remove or overwrite those styles so that the original styling of my screens come back? Images and Code below.
This is the stack navigator
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import LoginScreen from '../screens/LoginScreen';
import SignUpScreen from '../screens/SignUpScreen';
const Stack = createNativeStackNavigator();
const AuthStack = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Login" screenOptions={{ header: () => null }}>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Signup" component={SignUpScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default AuthStack;
When I only render the LoginScreen it looks like this
When I use AuthStack it looks like this
Should have read the docs nicely it was there in the NativeStackNavigator options.
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import LoginScreen from '../screens/LoginScreen';
import SignUpScreen from '../screens/SignUpScreen';
const Stack = createNativeStackNavigator();
const AuthStack = () => {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Login"
screenOptions={{
headerShown: false,
header: () => null,
contentStyle: { backgroundColor: 'white' },
}}
>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Signup" component={SignUpScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default AuthStack;
To do your own style do this
import { createStackNavigator } from "#react-navigation/stack";
import { NavigationContainer, DefaultTheme } from "#reactnavigation/native";
const MyTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: Color.red,
},
};
Then do this
<NavigationContainer theme={MyTheme}>
<Stack.Navigator initialRouteName="startscreen" headerMode="none" >
</Stack.Navigator>
</NavigationContainer>
More information here (https://reactnavigation.org/docs/themes/)

How to Access the navigation prop from any component?

I have tried multiple ways in different stackoverflow post and also the original documentation from https://reactnavigation.org/docs/connecting-navigation-prop/
but still failed to solve my issue.
I have a Login Screen, after i navigate to a Home Screen,
From Home screen i wanted to navigate to another screen but the navigation props is undefined.
Tried following the documentation but still failed to do so.
Please help thank you so much.
I have imported my App.js, HomePage.js (The one i wanted to navigate to another Screen but failed to)
Currently the code i have is based on the documentation but is having no effect when button is clicked.
import * as RootNavigation from '../config/RootNavigation';
const HomePage = () => {
const onLogOut = () => {
alert("Logged out")
firebase.auth().signOut();
}
const inventoryOnPress = () => {
RootNavigation.navigate('InventoryScreen')
}
return(
<View>
<CardComponent
style={styles.cardBackground}
title="Inventory"
onPress={inventoryOnPress}/>
</View>
);
}
export default HomePage;
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { navigationRef } from './src/config/RootNavigation';
const Stack = createStackNavigator();
function NavStack() {
const [initialPage,setInitialPage] = useState('LoginScreen');
return(
<Stack.Navigator
initialRouteName={initialPage}
screenOptions={{
headerTitleAlign:'center',
headerStyle: {
backgroundColor: '#621FF7'
}
}}
>
<Stack.Screen
name="LoginScreen"
component={LoginPage}
/>
<Stack.Screen
name="HomeScreen"
component={HomePage}
headerLeft={null}
/>
<Stack.Screen
name="SignUpScreen"
component={SignUpPage}
/>
<Stack.Screen
name="InventoryScreen"
component={InventoryPage}
/>
</Stack.Navigator>
)
}
function App() {
return (
<NavigationContainer ref={navigationRef}>
<NavStack />
</NavigationContainer>
);
}
}
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
// add other navigation functions that you need and export them
Please don't post firebase or any other API key on SO.
There are a few issues how you are handling navigation (authentication) here.
Please read the docs Auth Flow for detailed answer. One issue is when user is logged in you are returning just the HomeScreen and there is no NavigationContainer returned, this is one of the reason you don't have access to navigation prop in HomePage.
So basically in your App() fn you can return something like this
<NavigationContainer>
<Stack.Navigator>
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="SignUpScreen" component={SignUpPage} />
</>
)
</Stack.Navigator>
</NavigationContainer>
Now to get navigation prop in your HomeScreen you can use useNavigation Hook if you prefer hooks.
import { useNavigation } from '#react-navigation/native';
const HomePage = () => {
const navigation = useNavigation();
return (
<Button
title="Navigate To Profile"
onPress={() => navigation.navigate("Profile")}
/>
);
}
navigation prop is available to every individual screen enclosed in NavigationContainer.
You can access it as follows
const inventoryOnPress = () => {
props.navigation.navigate('InventoryScreen')
}
Refer this article for more detailed information for same.
Update
If you are not able to get navigation prop using ref to NavigationContainer then it implies your component is not rendered yet and has applied some actions on it.
So to avoid this use navigationRef.current.getRootState() to see if this returns an valid object and navigate consequently.

How do I properly define screens for React Native Navigation? (Error: Couldn't find any screens for the navigator. Have you defined any screens...)

First off I have looked at Error: Couldn't find any screens for the navigator. Have you defined any screens as its children?
And that person had an array of screens that was not yet filled. I have defined my screens and using them directly.
I'm new to React Native, and I'm trying to add navigation to the basic template. It's not going well.
My code is as follows:
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow strict-local
*/
import React from 'react';
import type {Node} from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Button,
} from 'react-native';
const Stack = createStackNavigator();
const HomeScreen = ({ navigation }) => {
return (
<Button
title="Go to Jane's profile"
onPress={() =>
navigation.navigate('Profile', { name: 'Jane' })
}
/>
);
};
const ProfileScreen = ({ navigation, route }) => {
return <Text>This is {route.params.name}'s profile</Text>;
<Button
title="Go to Home"
onPress={() =>
navigation.navigate('Home')
}
/>
};
const App: () => Node = () => {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Welcome' }}
key="1"
/>
<Stack.Screen name="Profile" component={ProfileScreen} key='2' />
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
At which point I get the error:
Error: Couldn't find any screens for the navigator. Have you defined any screens as its children?
I assume there's something basic I'm missing.
the component that is your screen need to return only one element.
If you need to return more than one element you can wrap it in the <> </> balise or any other component.
const ProfileScreen = ({ navigation, route }) => {
return (
<>
<Text>This is {route.params.name}'s profile</Text>
<Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
</>
);
};

React-Native nested navigation not working in expo

I'm using the React Native Stack Navigation to configure the header in my app and then nest a Drawer Navigation inside of it.
In the android emulator, everything is working fine. But whenever I try to open the app using expo, there's nothing more then a white blank screen. Developer tools aren't logging any errors, Expo itself doesn't give me an error nor does the terminal.
I tried replacing the whole navigation with just a <Text> component and in this case Expo shows the text. But I can't seem to find what I'm doing wrong. Some help would be much appreciated since I'm just learning React Native.
This is my code:
index.tsx
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
App.tsx
import React, {Component} from 'react';
import {NavigationContainer} from '#react-navigation/native';
import RootStack from './src/__plugins/navigation';
export default class App extends Component {
render() {
return (
<NavigationContainer>
<RootStack />
</NavigationContainer>
);
}
}
navigation/index.tsx
import {createStackNavigator} from '#react-navigation/stack';
import SignOutModalScreen from '../../_view/SignOutModalScreen';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {FontAwesomeIcon} from '#fortawesome/react-native-fontawesome';
import {faBars, faSignOutAlt, faTimes} from '#fortawesome/free-solid-svg-icons';
import {StyleSheet} from 'react-native';
import StartDrawer from './StartDrawer';
import {DrawerActions} from '#react-navigation/native';
export enum RoutingStack {
START = 'start',
GAME = 'main'
}
export enum RoutingDrawer {
START = 'start',
GAME = 'game'
}
export enum RoutingIdentifier {
JOIN_SCREEN = 'join',
GAME_SCREEN = 'game',
HELP_SCREEN = 'help',
SIGNOUT_SCREEN = 'sign_out'
}
const Stack = createStackNavigator();
const RootStack = () => {
return (
<Stack.Navigator
mode="modal"
screenOptions={({ navigation }) => ({
headerStyle: {
backgroundColor: '#80cbc4',
},
headerLeft: () => {
return (
<TouchableOpacity onPress={() => navigation.dispatch(DrawerActions.toggleDrawer)}>
<FontAwesomeIcon icon={faBars} style={styles.menuIcon} />
</TouchableOpacity>
);
},
headerRight: () => {
return (
<TouchableOpacity onPress={() => navigation.navigate(RoutingIdentifier.SIGNOUT_SCREEN)}>
<FontAwesomeIcon icon={faSignOutAlt} style={styles.signOutIcon} />
</TouchableOpacity>
);
},
})}
>
<Stack.Screen
name={RoutingDrawer.START}
component={StartDrawer}
options={{ title: '' }}
/>
<Stack.Screen
name={RoutingIdentifier.SIGNOUT_SCREEN}
component={SignOutModalScreen}
options={({ navigation }) => ({
headerTitle: '',
headerStyle: {
elevation: 0,
backgroundColor: '#F5FCFF',
},
headerLeft: () => {
return (
<TouchableOpacity onPress={() => navigation.navigate(RoutingDrawer.START)}>
<FontAwesomeIcon icon={faTimes} style={styles.closeIcon} />
</TouchableOpacity>
);
},
})}
/>
</Stack.Navigator>
);
};
const styles = StyleSheet.create({
closeIcon: {
marginStart: 10,
color: 'black',
},
menuIcon: {
marginStart: 10,
color: 'white',
},
signOutIcon: {
marginEnd: 10,
color: 'white',
},
});
export default RootStack;
And the StartDrawer.tsx
import {createDrawerNavigator} from '#react-navigation/drawer';
import {RoutingIdentifier} from './index';
import JoinPage from '../../join/_view/JoinScreen';
import {FontAwesomeIcon} from '#fortawesome/react-native-fontawesome';
import {faQuestionCircle, faSignInAlt} from '#fortawesome/free-solid-svg-icons';
import {StyleSheet} from 'react-native';
import {trans} from '../i18n';
const Drawer = createDrawerNavigator();
const StartDrawer: FC = () => {
return (
<Drawer.Navigator drawerType="slide" hideStatusBar={false}>
<Drawer.Screen
name={RoutingIdentifier.JOIN_SCREEN}
component={JoinPage}
options={{
drawerLabel: trans.getString('MENU_START_GAME'),
drawerIcon: () => (
<FontAwesomeIcon icon={faSignInAlt} style={styles.icon} />
),
}}
/>
<Drawer.Screen
name={RoutingIdentifier.HELP_SCREEN}
component={() => null}
options={{
drawerLabel: trans.getString('MENU_HELP'),
drawerIcon: () => (
<FontAwesomeIcon icon={faQuestionCircle} style={styles.icon} />
),
}}
/>
</Drawer.Navigator>
);
};
const styles = StyleSheet.create({
icon: {
marginEnd: -20,
marginStart: 10,
},
});
export default StartDrawer;
I am not sure but have you tried the opposite? stack inside drawer instead of drawer inside of stack?
I have had good results with this. Perhaps my root navigator may help you. Try using it as a template (I have written this code some time ago so I don't remember specifics but it works well) :
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { fetchPaymentMethods } from 'reducers/paymentMethods';
import { fetchNextPayments } from 'reducers/nextPayments';
import Home from 'pages/home';
import Orders from 'pages/orders';
import Settings from 'pages/settings';
import Scanner from 'pages/scanner';
import Support from 'pages/support';
import SideDrawer from 'components/SideDrawer';
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const StackNavigator = () => (
<Stack.Navigator headerMode="none">
<Stack.Screen name="Home" component={Home} />
<Drawer.Screen name="Orders" component={Orders} />
<Drawer.Screen name="Settings" component={Settings} />
<Drawer.Screen name="Scanner" component={Scanner} />
<Drawer.Screen name="Support" component={Support} />
</Stack.Navigator>
);
const RootNavigator = props => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchNextPayments());
dispatch(fetchPaymentMethods());
}, []);
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="Home"
screenOptions={{ gestureEnabled: true }}
drawerContent={props => <SideDrawer {...props} />}
>
<Drawer.Screen name="Root" component={StackNavigator} />
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Orders" component={Orders} />
<Drawer.Screen name="Settings" component={Settings} />
<Drawer.Screen name="Scanner" component={Scanner} />
<Drawer.Screen name="Support" component={Support} />
</Drawer.Navigator>
</NavigationContainer>
);
};
export default RootNavigator;

How do I use standalone Header in react-navigation with react-native?

I have a standalone Header that I made:
import React, { useContext } from "react";
import { Appbar } from "react-native-paper";
import { UserContext } from "../contexts/UserContext";
import { LanguageContext } from "../contexts/LanguageContext";
import localeSelect from "../services/localeSelect";
import { title } from "../data/locales";
function Header() {
const { user } = useContext(UserContext);
const { language } = useContext(LanguageContext);
return (
<Appbar.Header>
<Appbar.Action icon="menu" />
{!user && (
<>
<Appbar.Content
title={localeSelect(language, title)}
color="#ffffff"
/>
<Appbar.Action
icon="login"
color="#ffffff"}
/>
<Appbar.Action icon="account-plus" color="#ffffff" />
</>
)}
{user && (
<>
<Appbar.Content
title={localeSelect(language, title)}
color="#ffffff"
/>
</>
)}
</Appbar.Header>
);
}
export default Header;
However, I have been struggling to find a way to connect it to my Stack.Navigator in the main component:
import "react-native-gesture-handler";
import React, { useContext } from "react";
import { NavigationContainer } from "#react-navigation/native";
import { createStackNavigator } from "#react-navigation/stack";
import { View } from "react-native";
import Header from "./components/Header";
import Home from "./components/Home";
import Login from "./components/Login";
import GameNotes from "./components/GameNotes";
import { UserContext } from "./contexts/UserContext";
const Stack = createStackNavigator();
export default function ComponentContainer() {
const { user } = useContext(UserContext);
return (
<View>
<NavigationContainer>
<Header />
<Stack.Navigator initialRouteName="Home">
{user ? (
<Stack.Screen name="Home" component={GameNotes} />
) : (
<Stack.Screen name="Home" component={Home} />
)}
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
</View>
);
}
Obviously this doesn't work, because the header isn't nested in the Stack.Navigator. You also cannot nest it in the Stack.Navigator, because it only accepts Stack.Screen as children.
I've also tried using RootNavigation as a way to do this, but have failed with that as well. So what exactly am I supposed to be doing to use this Header to navigate my app?
So you want to change something in the Stack.Navigator when some action happens in the Header? Most likely what you need is to pass a callback to the Header component from the ComponentContainer, which Header will call when a user clicks something. Inside this callback you can modify your state in order to change the Stack.Navigator or take some other action. The callback will be defined inside ComponentContainer.
I'm not exactly sure how this all works in react-native, but in React this is what it might look like:
Header:
// Imports
function Header(onPressCallback) {
const { user } = useContext(UserContext);
const { language } = useContext(LanguageContext);
return (
<Appbar.Header>
<Appbar.Action icon="menu" onPress={() => onPressCallback('menu')} />
// Other Header items
</Appbar.Header>
);
}
export default Header;
ComponentContainer:
// Imports
const Stack = createStackNavigator();
const onPressHandler = (headerItemName) => {
// Do something in response to a user clicking on the "menu" icon for example
// Maybe something like change the route on the Stack (don't know this API)
}
export default function ComponentContainer() {
const { user } = useContext(UserContext);
return (
<View>
<NavigationContainer>
<Header onPressCallback={onPressHandler} />
<Stack.Navigator initialRouteName="Home">
{user ? (
<Stack.Screen name="Home" component={GameNotes} />
) : (
<Stack.Screen name="Home" component={Home} />
)}
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
</View>
);
}
This is how you usually handle interactions between two siblings components, the parent component passes a callback to one, so that the first child can notify when something happened, and then the parent takes action by changing the state for the second child to respond to.

Categories

Resources