How to call drawer navigator inside function in react native - reactjs

I am new to React-native. So I don't know much about navigation. When I click my image I need to open my drawer side menu.
I called my drawer inside the function.
const Drawer = createDrawerNavigator();
function TopBar(props) {
function sideMenuHandler() {
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="SideBar" component={SideBar} />
</Drawer.Navigator>
</NavigationContainer>
);
}
return (
<>
<View>
<TouchableWithoutFeedback onPress={sideMenuHandler}>
<Image source={require('../assets/profile.jpg')} onPress{sideMenuHandler}/>
</TouchableWithoutFeedback>
</View>
</View>
</>
);
}

First you have to describe your Drawer at the Root of your application as desribed in the docs.
Answering your question opening, toggling or closing a Drawer pane programmically you can use DrawerActions or the shortcut described below.
Import useNavigation to access navigation props.
import { useNavigation } from '#react-navigation/core';
Apply Actions by dispatching them:
const navigation = useNavigation();
const handlePress = () => {
navigation.toggleDrawer();
}

Related

React Navigation routes disappear from history on navigate

I have a screen in my app (CameraScreen) that is both in my Tab Navigator and Stack Navigator shown below (there are some more Tab Screens and Stack Screens that I removed for simplicity):
const TabNavigator = () => {
return (
<Tab.Navigator>
<Tab.Screen
name="Camera"
component={CameraScreen}
/>
</Tab.Navigator>
);
};
const Navigation = () => {
return (
<NavigationContainer theme={Theme}>
<Stack.Navigator headerMode="none">
<Stack.Screen name="Camera" component={TabNavigator} />
<Stack.Screen name="Product" component={ProductScreen} />
<Stack.Screen name="CameraStack" component={CameraScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
Now if I navigate to ProductScreen, then navigate to CameraStack from there and check the navigation state I notice that ProductScreen is nowhere to be found in the routes which I checked using navigation.getState().routes. Thus if I try and use navigation.goBack() it does not go back to the ProductScreen (which would be the expected behaviour).
When I check the routes in ProductScreen, ProductScreen shows up as the last route, however this disappears when I navigate to CameraStack.
I have a hunch that this has to do with the fact that CameraScreen is in both the Tab Navigator and Stack Navigator so for some reason the navigation prop passed to Camera is the Tab Navigator.
For reference my CameraScreen (simplified):
const CameraScreen = ({ navigation, route }) => {
// this doesn't include ProductScreen even if I navigate to CameraStack from the ProductScreen
console.log(navigation.getState().routes);
return (
<View></View>
);
};
and ProductScreen (simplified):
const ProductScreen = ({ navigation }) => {
return (
<View>
<TouchableOpacity
onPress={() => navigation.navigate("CameraStack")}
>
</TouchableOpacity>
</View>
);
};
One idea I can think of to resolve this issue is to manually pass a navigation parameter from ProductScreen but I'm wondering if there is a better way to handle this issue.
I realized this had nothing to do with the fact that I had this screen in both the Tab Navigator and Stack Navigator but was occurring because I was navigating to the same screen using navigation.navigate instead of navigation.push.
The former navigates to the previous location in your stack if you navigate to a screen you've already visited before, but the latter pushes a new route to the navigation state even if it's a screen you've already visited.
Handle backBehavior prop in your Navigator
<Tab.Navigator backBehavior='history'>
<Screen ... />
<Screen ... />
<Tab.Navigator/>
https://reactnavigation.org/docs/bottom-tab-navigator/#backbehavior

How to use React Navigation Drawer with Next.js?

I'd like to use the React Navigation v5 (Drawer navigation) with Next.js but I have a question about their integration.
In short: React Navigation (Drawer navigation) based on the React Navigation Screens component.
It conditionally renders the correct Screen.
The problem is: Next.js has it's own routing system based on the Folder structure. eg. each file in /pages folder automatically generates an appropriate route so I can't add these files as a React Navigation Screen (at least I'm not sure it's possible at all)
How to make these tools to work together and save the Next.js SSR feature?
Example of the React Navigation Drawer:
import * as React from 'react';
import { Button, View } from 'react-native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { NavigationContainer } from '#react-navigation/native';
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
onPress={() => navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}
function NotificationsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button onPress={() => navigation.goBack()} title="Go back home" />
</View>
);
}
const Drawer = createDrawerNavigator();
export default function App() {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Notifications" component={NotificationsScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
Thanks for any help!
You should use file based routing system from Nextjs on web and do your own navigation on mobile using React Navigation.
Below is my approach,
// this is how your directory might look like
- pages/
- index.tsx // this is your entry point for web
- about.tsx
App.tsx // this is your entry point for native
// pages/index.tsx
import React from 'react';
import { Text, View } from 'react-native';
const Home: React.FC = () => (
<View>
<Text>Welcome to Expo + Next.js 👋</Text>
</View>
);
export default Home;
// pages/about.tsx
import React from 'react';
import { Text, View } from 'react-native';
const About: React.FC = () => (
<View>
<Text>This is about page!</Text>
</View>
);
export default About;
Define your navigator for native app in App.tsx, it will only work on mobile so it doesn't have to be the same as what you have in pages/ folder. (actually if you only want your app run in browser, you don't need it at all.
Nextjs will handle all the route things, SSR etc... just like a normal Nextjs app when you run it in a browser.
// App.tsx
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import Home from '../pages/index';
import About from '../pages/about';
const Drawer = createDrawerNavigator();
const App: React.FC = () => (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="About" component={About} />
</Drawer.Navigator>
</NavigationContainer>
);
export default App;
The important thing is how should you change routes when you have your navigation on native app but an automatically routing system on web?
There is a package to solve this expo-next-react-navigation, check the documentation for details! Make sure you're using the correct version of this package, if you're using React Navigation 5, you should install expo-next-react-navigation#1.1.6 at this moment.
And here is an example, it should work on both platforms,
import React from 'react';
import { FlatList, Text } from 'react-native';
import { Link } from 'expo-next-react-navigation';
const links = [
{ key: 'home', route: '' },
{ key: 'about', route: 'about' },
];
const Links: React.FC = () => (
<FlatList
data={links}
renderItem={({ item }) => (
<Link routeName={item.route}>
{item.key}
</Link>
)}
/>
);
export default Links;

React Native Stack Navigation With Class Component [duplicate]

This question already has answers here:
What is the best way to redirect a page using React Router? [closed]
(5 answers)
Closed 1 year ago.
I am currently building a react native app and am using a stack navigator to navigate between the screens in my app. In my App.js, I am currently storing the screens in this format:
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="screen1">
<Stack.Screen name="screen1" component={Screen1}></Stack.Screen>
<Stack.Screen name="screen2" component={Screen2}></Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
After the user is in screen 1, I want to be able to navigate to screen 2 on the press of a button. I read through the documentation and I only saw examples on how to do this with functional components, for example:
function Screen1({ navigation }) {
return (
<View>
<Button title="Go to Home" onPress={() => navigation.navigate('screen2')} />
</View>
);
}
But how can I do this with a class component:
class Screen1 extends Component {
render() {
return(
<View>
// This does not work because I do not know how to pass in the navigation object
<Button title="Go to Home" onPress={() => navigation.navigate('screen2')} />
</View>
)
}
}
Where do I pass in the { navigation } ?
You dont have to pass navigation, its passed as a prop.
You can access it like below
this.props.navigation.navigate('nextScreen')
1 : import { Link, withRouter } from 'react-router-dom';
2 : export default withRouter(Component);
Login = () => {
this.props.history.push("/login")
}

React Native Navigation override goBack() of header back button in Stack Navigator

I want to customize the behavior of the default back button in stack navigator locally to one screen.
In the details assuming that on the stack there are screen1|screen2, I want to pass some props from screen2 to screen1 once the button is pressed.
I spent a lot of time reading React navigation docs, searching on internet and coding but I am not able to do this.
FROM DOCS
It's possible that in some circumstances that you want to customize the back button more than you can through the options mentioned above, in which case you can set the headerLeft option to a React Element that will be rendered
I know that the issue concerns the goBack() function of the headerRight component.
I want to override the default function goBack() related to the headerLeft back button with something like navigation.navigate("previousScreen",{{..props}}).
And ( this is very important!! ) I want to use this behavior locally to a specific screen, so not globally.
I have tried with something like this but doesn't works.
export default function App(){
return(
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="FirstScreen" component={FirstScreen}/>
<Stack.Screen name="SecondScreen" component={SecondScreen} options={{headerLeft: () => (
<HeaderBackButton
onPress={() =>navigation.navigate("FirstScreen",{//stuff//})}
title="Info"
color="#fff"
/>
),}}/>
</Stack.Navigator>
</NavigationContainer>
)}
If you are using react navigation v5, you can set the behaviour for a specific screen using :
import { HeaderBackButton } from '#react-navigation/stack';
...
options={{
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
navigation.navigate('screenName');
}}
/>
),
}}
...
You can also set as stack level using screenOptions={{}} instead.
"navigation" and "route" are also available on screen props.
options={({ navigation, route }) => ({headerLeft: ...})
For react-navigation v6, you can use setOptions inside a useEffect
import { HeaderBackButton } from '#react-navigation/elements';
...
const MyScreen = ({navigation}) => {
useEffect( () => {
navigation.setOptions({ headerShown: true,
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
navigation.navigate('MyOtherScreen');
}}
/>
)
});
} );
}
When the component is mounted, register back button press listener
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
}
Then in the function, you can check and handle the action, ex:
onBackButtonPressAndroid = () => {
// Check if the current screen is focused or a subscreen is perhaps
if (this.props.navigation.isFocused()) {
if (someCondition) {
// Do a navigation to a custom screen, pass props here if needed
this.props.navigation.navigate('search')
return true
// Return true if you want to tell react navigation you've handled the back press
}
else if (this.state.offsetTop > 10) {
// Ex. for scrolling to top
this.scrollToTop()
this.setState({
offsetTop: 0,
})
return true
}
}
// Return false if you want to tell react navigation you didn't handle the back press
return false
}
Try passing navigationOptions to a specific screen :
export default function App(){
return(
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="FirstScreen" component={FirstScreen}/>
<Stack.Screen name="SecondScreen" component={SecondScreen}
navigationOptions: ({ navigation }) => ({ headerLeft: (<HeaderBackButton onPress={() => {}}/>)
})}}/>
</Stack.Navigator>
</NavigationContainer>
)}
Either you can specify it there on the navigation.
Or on the Second screen, try this :
class SecondScreen extends React.Component {
static navigationOptions = {
headerLeft: (
<Button
onPress={() => alert('This is a back button!')}
title="Title"
color="#fff"
/>
),
};
}

How to pass the navigator object to the components used in the react natives root file?

Below is my app component:
/**
* Class app from where the app bootstraps
*/
export default class App extends Component {
constructor(props) {
super(props);
}
// This is where all your routes will be processed
renderScene(route, navigator) {
// Set a variable to get the route
let RouteComponent = route.component;
_navigator = navigator;
// With props return the components
return (
<RouteComponent navigator={navigator} {...route.passProps} />
);
}
static navigationBarRouteMapper = openControlPanel => ({
LeftButton: function(route, navigator, index, navState) {
return (
// Hamburger icon which on clicked opens the menu
<TouchableOpacity style={navBarStyle.left} onPress={() => openControlPanel()}>
<View>
{hamburgerIcon}
</View>
</TouchableOpacity>
);
},
RightButton: function(route, navigator, index, navState) {
return (
// cart menu
<TouchableWithoutFeedback onPress={() => dismissKeyboard()}>
<View style={navBarStyle.right}>
{cartIcon}
<View style={navBarStyle.counter}>
<Text style={navBarStyle.counterText}>20</Text>
</View>
</View>
</TouchableWithoutFeedback>
);
},
Title: function(route, navigator, index, navState) {
// Title of the route
return (
<TouchableWithoutFeedback onPress={() => dismissKeyboard()}>
<View style={navBarStyle.titleWrap}>
<Text style={navBarStyle.title}>{route.title.toUpperCase()}</Text>
</View>
</TouchableWithoutFeedback>
);
}
})
// Close the menu drawer
closeControlPanel() {
this._drawer.close();
}
// open the menu drawer
openControlPanel() {
this._drawer.open();
dismissKeyboard();
}
// On clicking the menu item, this function routes to that menu item page
getNavigator(route) {
this.refs.navigator.replace(route);
this.closeControlPanel();
}
render() {
return (
<Drawer
ref={ (ref) => { this._drawer = ref; } }
type="overlay"
content={<Menu navigator={this.getNavigator.bind(this)} menuItems={menu} closeControlPanel={this.closeControlPanel.bind(this)} />}
onOpenStart={() => dismissKeyboard()}
tapToClose={true}
openDrawerOffset={0.2}
panCloseMask={0.2}
panOpenMask={20}
acceptPan={true}
closedDrawerOffset={-3}
styles={drawerStyle}
tweenHandler={(ratio) => ({
// This code will maintain the opacity for main
// Whilst the opacity for the mainOverlay on the screen will be faded.
main: { opacity: 1 },
mainOverlay: {
opacity: ratio / 2,
backgroundColor: 'black',
}
})}>
<Navigator
initialRoute={homeScene}
renderScene={this.renderScene}
ref="navigator"
// Navbar of the app
navigationBar={
<Navigator.NavigationBar
routeMapper={App.navigationBarRouteMapper(this.openControlPanel.bind(this))}
style={navBarStyle.navBar}
/>
}
/>
<EditSearch />
</Drawer>
);
}
}
At the very bottom you'll see the <EditSearch> component which contains two text inputs. This component would be common to all the pages in the app except the home page.
Hence, I wanna know on which page or scene am I currently on so that I could check whether the scene/page is the home page or not. If it's the home page I would hide the component else I would show it for all other pages.
I tried passing the navigator via ref like so:
<EditSearch nav={this.refs.navigator} />
But, I get undefined on the <EditSearch> component and the view doesn't re-render when the page changes as it doesn't detect any state change.
I can do something like:
this.state = {
currentRoute: 'home'
}
And then change this state when route changes. But, I cannot change state within renderScene as setting state in renderScene will lead into infinite loop. If I could set this state with the title of the page when route changes, I could then send that state to <EditSearch> component.
I am pretty confused how can I pass current route information to this common component. Thanks in anticipation.
Okay, I found a solution but I am unsure if it's appropriate.
So below is my solution.
First I created the state and function to set that state:
constructor(props) {
super(props);
this.state = {
isHome: false
};
this.setIsHome = this.setIsHome.bind(this);
}
setIsHome(flag) {
// Set state to let component know whether the page is home or not
this.setState({
isHome: flag ? flag : false
});
}
Then passed the function to all the pages where those pages can either hide or show <EditSearch> component:
<RouteComponent navigator={navigator} {...route.passProps} setIsHome={this.setIsHome} />
Then I passed the isHome state to the <EditSearch> component:
<EditSearch isHome={ this.state.isHome } />
And this is how I call the function in the home component:
componentWillMount() {
this.props.setIsHome(true);
}
componentWillUnmount() {
this.props.setIsHome(false);
}
This is how I show/hide the component. I hope it's helpful. But, would be really nice to know how to pass navigator object to such components.
Here's a solution from Drawer Github issues.
Found a quick workaround for this but it is not ideal. If you wrap the
Navigator with the Drawer component, then inside of the
Navigator.NavigationBar you can set this.navigator = this.navigator ||
navigator and then you can pass the navigator to your content
component for the Drawer.
<Drawer
content={<Main navigator={this.navigator}/>}
type='overlay'>
<Navigator
renderScene={renderScene}
navigationBar={
<Navigator.NavigationBar
routeMapper={{
LeftButton: (route, navigator, index, navState) => {
this.navigator = this.navigator || navigator;
return (
<View>
<Text>Something</Text>
</View>
);
}
}}
/>
/>
</Drawer>
https://github.com/root-two/react-native-drawer/issues/187#issuecomment-231461585

Resources