Drawer Navigate inside a stack Navigator - react-native

Inside my Home (Stack) page I want to create a drawer to show more information options that will be routes, is there a possibility to create a drawer inside this Home(Stack) to navigate to other routes?
import { api } from '../../services/api'
import Cultural from '../Cultural'
import Religioso from '../Religioso'
export default function Home() {
const Drawer = createDrawerNavigator()
const { user } = useContext(AuthContext)
const [teste, setTeste] = useState([])
async function loadingRequest() {
const res = await api.get('/religioso')
setTeste(res.data)
}
useEffect(() => {
loadingRequest()
console.log(teste)
}, [])
console.log(teste)
return (
<SafeAreaView>
<ContainerCard>
<Image style={{ width: '100%', height: 120, backgroundColor: 'red' }} />
</ContainerCard>
<NavigatorContainer>
<Drawer.Navigator>
<Drawer.Screen name='Cultural' component={Cultural} />
<Drawer.Screen name='Religioso' component={Religioso} />
</Drawer.Navigator>
</NavigatorContainer>
</SafeAreaView>
)
}```

Here is the react navigation documentation on nesting navigators.
https://reactnavigation.org/docs/nesting-navigators/
Article TL;DR
Based on your question, it looks like an example of nested navigation in your react native app.
Your root app contains page Home, and this Home page has pages Cultural, Religioso.
You can nest your navigation pages as follows:
function Home() {
return (
<Drawer.Navigator>
<Drawer.Screen name="Cultural" component={Cultural} />
<Drawer.Screen name="Religioso" component={Religioso} />
</Drawer.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
/>
{/** Other stack screens here */}
</Stack.Navigator>
</NavigationContainer>
);
}

Related

How to current route name in react-navigation inside navigation.js file?

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} />
)}
/>

Using two Navigators in an app React native

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.

How to hide some pages from Drawer Navigation but still be able to navigate to them - React Native?

I want to hide some pages from the Drawer
I want to hide some pages from the Drawer (for example hide the SignUpPage and SuccessPage), how can I do it ?
i also tried to make an anonymous function in the DrawerLabel [ ()=> null ] but it is still not a good solution because even tho it shows me an empty label, yet when i click on it , it navigates me to the page that i wanted to hide.
Please help
and thanks for all the helpers :)
import { createDrawerNavigator } from '#react-navigation/drawer';
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="WelcomePage">
//...all the pages
<Drawer.Screen
name="HomePage"
component={HomePage}
options={{ drawerLabel: 'Home Page' }}
/>
<Drawer.Screen
name="SignUpPage"
component={SignUpPage}
options={{ drawerLabel: 'SignUp Page' }}
/>
<Drawer.Screen
name="SuccessPage"
component={SuccessPage}
options={{ drawerLabel: 'SuccessPage' }}
/>
</Drawer.Navigator>
);
}
const Stack = createStackNavigator();
export default function App() {
return (
< NavigationContainer >
<DrawerNavigator>
<Stack.Navigator initialRouterName="WelcomePage">
<Stack.Screen name="WelcomePage" component={WelcomePage} />
>
<Stack.Screen name="SuccessPage" component={SuccessPage} />
<Stack.Screen name="HomePage" component={HomePage} />
</Stack.Navigator>
</DrawerNavigator>
</NavigationContainer >
);
}
You have differents options
I guess you want to hide that options when your user is signed or not. With v5 you can do the code below. The another option is the same but playing with custom content that is a bit complex also I give you the docs if you want the complex solution https://reactnavigation.org/docs/drawer-navigator.
DrawerNavigator
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="WelcomePage">
//...all the pages
<Drawer.Screen
name="HomePage"
component={HomePage}
options={{ drawerLabel: 'Home Page' }}
/>
</Drawer.Navigator>
);
}
AuthNavigator
const Stack = createStackNavigator<AuthParamList>();
export const AuthNavigator = () => {
return (
<Stack.Navigator headerMode='none'>
<Stack.Screen name='SignUpPage' component={SignUpPage}></Stack.Screen>
<Stack.Screen name='SuccessPage' component={SuccessPage}></Stack.Screen>
</Stack.Navigator>
);
};
IsAuthScreen, I use firebase + redux so here you need to put your login logic
const IsAuth: React.FC<RoutesProps> = (props) => {
const { eva, ...rest } = props;
const dispatch = useDispatch();
const onAuthStateChanged = (currentUser: any) => {
console.log("onAuthStateChanged -> currentUser", currentUser)
if (!currentUser) {
dispatch(new authActions.DidTryLogin());
} else {
if (!currentUser.emailVerified) {
dispatch(new authActions.DidTryLogin());
} else {
dispatch(new authActions.SigninSuccess(currentUser));
dispatch(new settingsActions.GetProfile(currentUser.uid));
}
}
};
useEffect(() => {
const subscriber = firebase.auth().onAuthStateChanged(onAuthStateChanged);
return () => {
subscriber();
}; // unsubscribe on unmount
}, [dispatch]);
return (<View >
<LoadingIndicator size='large' /> // Here put a loading component
</View>);
};
App component, I use redux for check if my user is logged so here you need to put your own logic
const isAuth = useSelector(selectAuthUser);
const didTryAutoLogin = useSelector(selectAuthDidTryLogin);
return (
<NavigationContainer>
{isAuth && <DrawerNavigator />}
{!isAuth && didTryAutoLogin && && <AuthNavigator />}
{!isAuth && !didTryAutoLogin && <IsAuthScreen />}
</NavigationContainer>);
So when you logout you don't need to navigate to SignInScreen (this will be a problem if you think about it because if you want to do that you can back to protected screens with the back button or gesture). You only need to update the state and the correct navigator will be in place and you can put the default screen to show. You can achieve this with redux or react context.

React navigation 5 - header is not shown

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>
);
}

How to use navigation.navigate from a component outside the stack.navigation

I have an application using React native where I am using react-navigation (5.2.9).
I built a Stack.Navigator where I've got my screens but I want the Footer component to be outside so it renders in all screens. The problem is, I can't navigate from the footer, which is what I need to do as the footer has a few buttons that should be changing the screen:
const Stack = createStackNavigator();
const App = () => {
return (
<Provider store={store}>
<NavigationContainer>
<Header />
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerShown: false
}}
/>
<Stack.Screen
name="Login"
component={LoginScreen}
options={{
headerShown: false
}}
/>
</Stack.Navigator>
<Footer />
</NavigationContainer>
</Provider>
);
};
How do I pass the navigation prop to the footer component?
Try this:
Create a new file named: RootNavigation.js
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
// file where you have the navigation
import {navigationRef} from './path/to/RootNavigation';
<NavigationContainer ref={navigationRef}>
.....
<Footer />
</NavigationContainer>
And Footer.js should be something like this:
// Footer.js
import React from 'react';
import {View, Button} from 'react-native';
import * as RootNavigation from './path/to/RootNavigation';
const Footer= () => {
return (
<View>
<Button
title={'Go to'}
onPress={() =>
RootNavigation.navigate('screenName ', {userName: 'Lucy'})
}
/>
</View>
);
};
export default Footer;
For more info you can read the documentation. https://reactnavigation.org/docs/navigating-without-navigation-prop/
The components outside of the Stack.Screen components do not receive the navigation prop. So in this case, you have to get the NavigationContainer using refs in your footer component, as follows:
Create a ref with const myRef = React.createRef()
pass it to the <NavigationContainer ref={myRef}>, and
use that ref to navigate, myRef.current?.navigate(name, params).
It is all explained here. The docs here separate the creation of the ref in a new file, to allow you to import the ref without dependency loops/ issues. As the docs state, you can now call RootNavigation.navigate('ChatScreen', { userName: 'Lucy' }); in any js module, not just in a react component.
In your case though, you don't need the ref in separate file.
const Stack = createStackNavigator();
const navigationRef = React.createRef();
const App = () => {
return (
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<Header />
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerShown: false
}}
/>
<Stack.Screen
name="Login"
component={LoginScreen}
options={{
headerShown: false
}}
/>
</Stack.Navigator>
<Footer navigationRef={navigationRef}/>
</NavigationContainer>
</Provider>
);
};
And in <Footer/> use navigationRef.current?.navigate(name, params)
From the documentation.
https://reactnavigation.org/docs/connecting-navigation-prop/
import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '#react-navigation/native';
function GoToButton({ screenName }) {
const navigation = useNavigation();
return (
<Button
title={`Go to ${screenName}`}
onPress={() => navigation.navigate(screenName)}
/>
);
}
I ended up building a component called screen that will just wrap the content of screen and render the header/footer based on props:
import React from 'react';
import { View } from 'native-base';
import style from './style';
import Footer from '../../footer';
import Header from '../../header';
const Screen = ({
footer,
header,
children,
navigation
}) => (
<View style={style.screen}>
{ header && <Header navigation={navigation} />}
{ children }
{ footer && <Footer navigation={navigation} />}
</View>
);
export default Screen;
And wrapping the screens of my apps like this:
<Screen header footer navigation={navigation}>
... screen content
</Screen>
I feel like it is the best way if I can't sort that problem out.
A simple trick to extract navigation out of screenOptions.
function NavWrapper() {
const navigatorRef = useRef<any>();
<Tab.Navigator
screenOptions={({ navigation: nav }) => navigatorRef.current = nav}
>
<Tab.Screen name="screen1" />
</Tab.Navigator>
}
I solve this problem with a global state using React context API:
// When enter in the HomeScreen:
const { setGlobalNavigation, globalNavigation } = useGlobalContext();
const navigation = useNavigation<RemindersNavigationProp>();
useEffect(() => {
if (setGlobalNavigation && !globalNavigation) setGlobalNavigation(navigation);
}, [setGlobalNavigation, globalNavigation, navigation]);
// When want to use:
const { globalNavigation } = useGlobalContext();
const handleNavigate = () =>
globalNavigation.navigate("contactsStack", { screen: "newContact" });