Trying to use bar Navigation and drawer navigator in the same app, and now sure how to make it work.
So currently In the App.js I have a "NavigationContainer", and inside I have a "BarNavigator". whcih works fine, then I wanna add a "DrawerNavigator" inside the "NavigationContainer" then I got an error "Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app"
then i found this link doing exactly what i wanted ( showing at the end of the page ) and apply what he is doing. still got the same error, then I removed the "BarNavigator" which is the working one, and test out if DrawerNavigator got error, and yes. even got error with the DrawerNavigator only. and here is the code.
App.js
const App = () => {
return (
<NavigationContainer>
//<HomeStackNavigator />
<DrawerNavigator />
</NavigationContainer>
);
};
export default App;
DrawerNavigator.js
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={navigation_bar} />
{/* <Drawer.Screen name="Notifications" component={Ivestment} /> */}
</Drawer.Navigator>
</NavigationContainer>
);
};
export default DrawerNavigator;
Navigator.js
const Stack = createStackNavigator();
const screenOptionStyle = {
headerShown: false,
};
const HomeStackNavigator = () => {
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen name="Home" component={BottomTabNavigator} />
<Stack.Screen name="Detail" component={Detail} />
</Stack.Navigator>
);
};
export default HomeStackNavigator;
You are doing it wrong.
You should have only 1 "NavigationContainer" and the navigators should be nested, i.e. one inside of another (parent-child not siblings).
It should look something like this:
App.js
const App = () => {
return (
<NavigationContainer>
<DrawerNavigator />
</NavigationContainer>
);
};
export default App;
DrawerNavigator.js
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
return (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeTabNavigator} />
<Drawer.Screen name="OtherScreen" component={OtherScreen} />
</Drawer.Navigator>
);
};
export default DrawerNavigator;
TabNavigator.js
const Tab = createTabNavigator();
const screenOptionStyle = {
headerShown: false,
};
const HomeTabNavigator = () => {
return (
<Tab.Navigator screenOptions={screenOptionStyle}>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Detail" component={Detail} />
</Tab.Navigator>
);
};
export default HomeTabNavigator;
Might be easier to look at it bottom-top as well.
Related
I want to know the current route name but inside the navigation js file, I use the useRoute hook in any component and work well, but I get this error when I use useRoute inside navigation.js
Error: Couldn't find a route object. Is your component inside a screen in a navigator?
navigation.js code example,
export default function Navigation() {
const route = useRoute(); // show error >> Error: Couldn't find a route object. Is your component inside a screen in a navigator?
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
</Stack.Navigator>
);
}
when I remove useRoute, the Error is gone, But I need to use useRoute or any other way to get current route name inside navigation.js file.
You can pass the navigationContainerRef from the NavigationContainer to the Navigation comomponent to make the navigation object accessible.
Consider the following code snippet.
import { createNavigationContainerRef } from "#react-navigation/native"
export const navigationRef = createNavigationContainerRef()
const App = () => {
return <NavigationContainer
ref={navigationRef}>
<Navigation navigation={navigationRef} />
</NavigationContainer>
}
export default App
Then, inside Navigation.
export default function Navigation({ navigation }) {
const route = navigation.current?.getCurrentRoute()
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
</Stack.Navigator>
);
}
The current route name get then be accessed using route?.name.
Edit: As jhon antoy correctly pointed out in the comments, this does not update the current route state if we navigate to a different screen. We need to update this ourselves as follows.
export const navigationRef = createNavigationContainerRef();
const App = () => {
const [routeName, setRouteName] = useState();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
setRouteName(navigationRef.getCurrentRoute().name)
}}
onStateChange={async () => {
const previousRouteName = routeName;
const currentRouteName = navigationRef.getCurrentRoute().name;
console.log("route", currentRouteName)
setRouteName(currentRouteName);
}}
>
<Navigation routeName={routeName} />
</NavigationContainer>
);
}
export default App;
Inside Navigation.
export function Navigation(props) {
const route = props.routeName
console.log(props)
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
</Stack.Navigator>
);
}
I have made a snack with some simple navigation buttons.
Works perfectly in v6.
In Your Stack, you have to convert the component to child.. in this way:
<RootStack.Screen
name={NameOfYourRoute}
children={props => (
<NameOfYourStack {...props} currentRoute={currentRoute} />
)}
/>
I would like to have stack and drawer navigators use the same navigation bar. However, both are creating their own navigation bars refer to the below image making the app look messy. How can I achieve that?
Below is my code:
import * as React from "react";
import { createStackNavigator } from "#react-navigation/stack";
import { createDrawerNavigator } from "#react-navigation/drawer";
import Home from "../screens/Home";
import Contact from "../screens/Contact";
import About from "../screens/About";
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const HomeNavigation = (props) => {
return (
<Stack.Navigator initialRouteName='Home'
screenOptions={{
headerTitleAlign:'center',
//headerShown: false
}}
>
<Stack.Screen name='Home' component={Home} />
<Stack.Screen name='Contact' component={Contact} />
<Stack.Screen name='About' component={About} />
</Stack.Navigator>
);
};
const MenuNavigation = (props) => {
return (
<Stack.Navigator initialRouteName='Home'
screenOptions={{
headerTitleAlign:'center',
//headerShown: false
}}>
<Stack.Screen name='Menu' component={About} />
</Stack.Navigator>
);
};
const DrawerNavigation = () => {
return (
<Drawer.Navigator>
<Drawer.Screen name='HomeNavigation' component={HomeNavigation} />
<Drawer.Screen name='MenuNavigation' component={MenuNavigation} />
</Drawer.Navigator>
);
};
export default DrawerNavigation;
I was using BottomTabNavigation to create tabs navigation in react native with nested Stack navigation, but when I want to implement the same logic with createMaterialTopTabNavigator, I get this error. here's the code :
const Stack = createNativeStackNavigator()
const TopTab = createMaterialTopTabNavigator()
const HomeStack = () => {
return <Stack.Navigator>
<Stack.Screen name="Home" component={Home} options={options} />
<Stack.Screen name="TodoDetail" component={TodoDetail} />
</Stack.Navigator>
}
export default function App() {
return (
<NavigationContainer>
<TopTab.Navigator>
<TopTab.Screen name='Home' component={HomeStack} />
<TopTab.Screen name='About' component={About} />
</TopTab.Navigator>
</NavigationContainer>
)
}
here is the error in the device :
The problem is not with the definition of TopTab, but the current implementation leads to the issue.
The TopTab navigation must be in a Stack.Screen. forget about the BottomTabNavigation, it uses a different approach to show the screens.
So You must use TopTabNavigation inside of a screen
Just for an example with the current implementation:
const Stack = createNativeStackNavigator()
const TopTab = createMaterialTopTabNavigator()
const topBarStack = () => {
return (
<TopTab.Navigator>
<TopTab.Screen name='first-tab' component={firstTab} />
<TopTab.Screen name='second-tab' component={secondTab} />
</TopTab.Navigator>
)
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={topBarStack} options={options} />
<Stack.Screen name="TodoDetail" component={TodoDetail} />
</Stack.Navigator>
</NavigationContainer>
)
}
I am new to the world of react native, so I need a hand with structuring my navigation pages. I want my app to have instagram like structure - that is the main page should be createBottomTabNavigator From there I have few different pages, and I can switch between them easily. Here is my code:
export default const MyTabs = ({ currentUser, navigation }) => {
return (
<Tab.Navigator initialRouteName="Home">
<Tab.Screen
name="Home"
component={HomeScreen}
/>
<Tab.Screen
name="Network"
component={NetworkScreen}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarIcon: ({ color, size }) => (
<Entypo name="map" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
};
Then in my App.js file I call it like this:
return (
<Provider store={store}>
<NavigationContainer>
<MyTabs />
</NavigationContainer>
</Provider>
);
The above code works fine. The problem is when I want to perform more complicated navigation. For instance, in my home page I have list of objects and once, I click on these objects I want to navigate to a new page - with Stack navigator. How do I do so, and where should I define this page? Do I define it somehow inside my Home page, or inside my App.js page? In my mind it should be something like this:
<Stack.Screen
name="Details"
component={DetailScreen}
/>
I tried to put it inside App.js, something like this:
<Provider store={store}>
<NavigationContainer>
<MyTabs />
<Stack.Navigator>
<Stack.Screen
name="Meetup Details"
component={MeetupDetails}
options={{
headerTitle: "Meetup Details",
}}
/>
</Stack.Navigator>
</NavigationContainer>
</Provider>;
But ofc it didn't work.
Also I have another question - in my bottom tab I can navigate to My profile screen and that works fine. But I want to view other profiles as well. So my bottom tab is reserved for my own profile, but how do I define another Profile screen, which can go to other user's profile? Does it need to be with stack navigator? Does it again need to be defined in App.js? Is it ok to define two different navigators (bottom tab and stack) with the same page (Profile)?
Usually, when you have a bottom tab navigator, each tab will be a StackNavigator
export default const MyTabs = ({ currentUser, navigation }) => {
return (
<Tab.Navigator initialRouteName="Home">
<Tab.Screen
name="Home" // here this would be BottomTabRoutes.HOME
component={HomeNavigator}
/>
<Tab.Screen
name="Network" // here this would be BottomTabRoutes.NETWORK
component={NetworkNavigator}
/>
<Tab.Screen
name="Profile" // here this would be BottomTabRoutes.PROFILE
component={ProfileNavigator}
options={{
tabBarIcon: ({ color, size }) => (
<Entypo name="map" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
};
And then your MeetupDetails screen will be contained inside the HomeNavigator which is a StackNavigator
export const HomeNavigator = () => {
return (
<Stack.Navigator
screenOptions={{
...
}}>
<Stack.Screen
name={HomeRoutes.HOME}
component={Home}
options={{
headerStyle: styles.header,
title: 'Home'
}}
/>
<Stack.Screen
name={HomeRoutes.MEETUP_DETAILS}
component={MeetupDetails}
options={{
headerStyle: styles.header,
title: 'Meetup Details'
}}
/>
</Stack.Navigator>
}
Also, I would suggest to store your routes names in an enum, in this way it will be easier and their possible route props in a tpye
Something like this:
export enum BottomTabRoutes {
HOME = 'Home',
NETWORK = 'Network',
PROFILE = 'Profile',
}
export type BottomTabRouteProps = {
[BottomTabRoutes.HOME]: {title: string}; // this is another parameter example
[BottomTabRoutes.PROFILE]: undefined;
[BottomTabRoutes.NETWORK]: undefined;
};
And the same for each Stack Navigator:
export enum HomeRoutes = {
HOME = 'Home',
MEETUP_DETAILS = 'Meetup Details',
}
export type HomeRouteProps = {
[HomeRoutes.HOME]: undefined
[HomeRoutes.MEETUP_DETAILS: { someParamName: SomeType } // this might be a parameter you'll send through navigation
}
// Therefore, your MeetupDetails screen will look something like this:
export const MeetupDetails = (props: StackScreenProps<HomeRouteProps, HomeRouteProps.MEETUP_DETAILS>,) => {
const navigationParam = props.route.params.someParamName
return (
<View>
...
</View>
)
}
EDIT
Regarding the Profile Screen, I believe you can have something like this:
export const MainNavigator = () => {
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name={MainNavigatorRoutes.HOME} component={HomeNavigator} />
<Stack.Screen name={MainNavigatorRoutes.USER} component={UserNavigator} />
</Stack.Navigator>
);
};
export const UserNavigator = () => {
return (
<Stack.Navigator
screenOptions={({navigation}) => ({
...)}>
<Stack.Screen
name={UserNavigatorRoutes.SOME_OTHER_SCREEN}
component={UserSettings}
/>
<Stack.Screen
name={UserNavigatorRoutes.USER_PROFILE}
component={UserProfile}
/>
</Stack.Navigator>
);
};
export const HomeNavigator = () => {
return (
<Stack.Navigator screenOptions={{title: ''}} >
<Stack.Screen
name={HomeNavigatorRoutes.HOME_TABS}
component={HomeTabNavigator}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
);
};
export const HomeTabNavigator = () => (
<Stack.Navigator >
<Stack.Screen
name={HomeTabNavigatorRoutes.TAB_NAVIGATOR}
component={MyTabs}
options={{headerShown: false}}
/>
</Stack.Navigator>
Basically, the structure will look like this: MainNavigator = { HomeNavigator, UserNavigator }. In this way, each navigator will be separate and you should be able to navigate to any screen from UserNavigator even if you are in a screen from UserNavigator
Trying to update my app to react navigation 5 and been confronting some issues.
First of all, the header does not show up. Snips from the code:
[from App.js]
const Tab = createBottomTabNavigator();
function App() {
return (
<NavigationContainer>
<Tab.Navigator >
<Tab.Screen name="Home" component={HomeScreen} options={{ title:'some title' }}/>
<Tab.Screen name="Upload" component={UploadScreen} />
<Tab.Screen name="Find" component={FindScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
export default App;
and the style of the current screen:
<View style={{flex:1, flexDirection:'column',justifyContent:'space-between'}}>
Here is a screenshot of the app on an Android emulator (and it looks the same on my phone):
As you can see, the header is not shown, the tab navgiation does not right, and so are the buttons (something changed about their background). I did not change anything in the app besides upgrading to react-navigation 5..
Thanks for the help!
Tab navigators do not have header support. You have to wrap your tab navigator inside a stack navigator.
import { createStackNavigator } from "#react-navigation/stack";
// ... other imports
export const App = () => {
return (
<NavigationContainer>
<StackNavigator />
</NavigationContainer>
);
}
const Stack = createStackNavigator();
const StackNavigator = () => {
return (
<Stack.Navigator>
<Stack.Screen name="Tabs" component={TabNavigator} />
</Stack.Navigator>
);
}
const Tab = createTabNavigator();
const TabNavigator = () => {
return (
<Tab.Navigator >
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Upload" component={UploadScreen} />
<Tab.Screen name="Find" component={FindScreen} />
</Tab.Navigator>
);
}