How to pass a function as a prop in React-Native - react-native

I'm currently working on an app in React-Native and it includes DrawerNavigation, SwitchNavigation and AppContainer. There is a method at header.js that i need to use in order to make the drawer functionable (toggleDrawer())
I've tried passing the function at the DrawerNavigator but it didnt work.
export default class Header extends React.Component {
render() {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => {
this.props.navigation.toggleDrawer();
}}
>
<Image
source={require("/Users/Rron/AnketaApp/assets/hamburger-
icon.jpg")}
style={styles.imageStyle}
/>
</TouchableOpacity>
</View>
);
}
}
});
export default class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
let drawerLabel = "Home";
return { drawerLabel };
};
render() {
return (
<View style={styles.container}>
<Header {...this.props}/>
<ScrollView>
<Content />
</ScrollView>
</View>
);
}
}
export default class DrawerNavigator extends React.Component {
render() {
return <AppContainer />;
}
}
const AppDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: HomeScreen
},
Anketa: {
screen: AnketaScreen
}
}
);
const AppContainer = createAppContainer(
createSwitchNavigator({
Introduction: {
screen: IntroductionScreen
},
Drawer: {
screen: AppDrawerNavigator``
}
})
);
The error says
this.props.navigation.toggleDrawer is not a function and its not
defined.

What you can do is import { DrawerActions } from 'react-navigation-drawer' and use it as it says in the docs.
this.props.navigation.dispatch(DrawerActions.toggleDrawer());
Also make sure that you components are inside the navigation.

Related

How to nest a component wrapping a stack navigator inside a tab navigator react

Current Behavior
I am trying to have a tab navigator, where one of the screens/tabs has a component, that, among other stuff, has a stack navigator in it.
However, I currently get a 'No "routes" found in navigation state' error.
How to reproduce
The code I'm currently running can also be found as a snack.
Code:
import * as React from 'react';
import { Button, Text, View, StyleSheet } from 'react-native';
import { createAppContainer, createStackNavigator, createBottomTabNavigator } from 'react-navigation';
class ScreenA extends React.Component {
static navigationOptions = {
tabBarLabel: 'A',
};
render() {
return (
<View>
<Text>Screen A</Text>
</View>
);
}
}
class SettingsHome extends React.Component {
render() {
return (
<Button onPress={() => this.props.navigation.navigate('SettingsScreenA')}>
<Text>Navigate to Settings A</Text>
</Button>
);
}
}
class SettingsScreenA extends React.Component {
render() {
return (
<View>
<Text>Settings A</Text>
<Button onPress={() => this.props.navigation.navigate('SettingsA')}>
<Text>Back to SettingsHome</Text>
</Button>
</View>
);
}
}
const SettingsStackNavigator = createStackNavigator({
SettingsHome: { screen: SettingsHome },
SettingsScreenA: { screen: SettingsScreenA }
})
class SettingsScreen extends React.Component {
static navigationOptions = {
tabBarLabel: 'Settings',
}
render() {
return (
<View>
<Text>Some other components...</Text>
<SettingsStackNavigator navigation={this.props.navigation}/>
</View>
);
}
}
const RootTabNavigator = createBottomTabNavigator({
ScreenA: { screen: ScreenA },
Settings: {screen: SettingsScreen },
});
const Navigation = createAppContainer(RootTabNavigator);
export default class App extends React.Component {
render() {
return (
<Navigation />
);
}
}
in this button you want to navigate to SettingsA
<Button onPress={() => this.props.navigation.navigate('SettingsA')}>
<Text>Back to SettingsHome</Text>
</Button>
but you defined the route of Settings screen as Settings
const RootTabNavigator = createBottomTabNavigator({
ScreenA: { screen: ScreenA },
Settings: {screen: SettingsScreen },
});
you have to fix it by change the SettingsA as route name in tab navigator
const RootTabNavigator = createBottomTabNavigator({
ScreenA: { screen: ScreenA },
SettingsA: {screen: SettingsScreen },
});

Show logged in user info in react-native drawerNavigator

I've been searching for a simple solution with best practices to have drawer that would show user's info, i.e. name, age, etc. The login will happen in a separate screen but after the login is done, somehow user info should be passed to drawerNavigator.
DrawerNav
- StackNav
- Screen1
- Screen2
- SettingsScreen (login will happen here)
It's really frustrating that I couldn't find a working solution yet.
Expo code: https://snack.expo.io/#alisalimi25/user-info-drawer
import React, { Component } from 'react';
import {
Button,
SafeAreaView,
ScrollView,
Text,
View
} from 'react-native';
import {
createDrawerNavigator, createStackNavigator,
DrawerItems, NavigationActions
} from 'react-navigation';
class Screen2 extends React.Component {
render() {
return(
<View><Text>Screen2</Text></View>
);
}
}
class Screen1 extends React.Component {
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Text>Screen1</Text>
<Button
onPress={() => this.props.navigation.openDrawer()}
title='Open Drawer' />
</SafeAreaView>
);
}
}
class SettingScreen extends React.Component {
loginUser = () => {
console.log('We need to pass user info into drawer navigator');
};
render() {
return (
<SafeAreaView>
<Text>Settings Page</Text>
<Button
onPress={() => this.loginUser()}
title='Login' />
</SafeAreaView>
);
}
}
const StackNav = createStackNavigator(
{
Screen1: Screen1,
Screen2: Screen2
}
);
const CustomDrawerContentComponent = (props) => {
return (
<ScrollView>
<SafeAreaView style={{ flex: 1 }} forceInset={{ top: 'always', horizontal: 'never' }}>
<Text>Hello USER_NAME_FROM_PROPS?</Text>
<DrawerItems {...props} />
</SafeAreaView>
</ScrollView>
)
};
const DrawerNav = createDrawerNavigator(
{
StackNav: {
screen: StackNav,
},
SettingScreen: {
screen: SettingScreen
}
},
{
contentComponent: CustomDrawerContentComponent
}
);
export default DrawerNav;
I'm sure the solution is somewhere but I couldn't find it yet. Any help is highly appreciated.
Thanks
So I managed to use screenProps to pass around parameters between pages but I'm not sure if it's a good pattern because it's like global variables and I think there is a chance of name collision between different layers of navigation. The working code is:
import React, { Component } from 'react';
import {
Button,
SafeAreaView,
ScrollView,
Text,
View
} from 'react-native';
import {
createDrawerNavigator, createStackNavigator,
DrawerItems, NavigationActions
} from 'react-navigation';
class Screen2 extends React.Component {
render() {
return(
<View><Text>Screen2</Text></View>
);
}
}
class Screen1 extends React.Component {
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Text>Screen1</Text>
<Button
onPress={() => this.props.navigation.openDrawer()}
title='Open Drawer' />
</SafeAreaView>
);
}
}
class SettingScreen extends React.Component {
constructor(props) {
super(props);
console.log('passed props for settingScreen are: ', props);
}
loginUser = () => {
console.log('We need to pass user info into drawer navigator');
this.props.screenProps.userId = 'Gandalf';
};
render() {
return (
<SafeAreaView>
<Text>Settings Page</Text>
<Button
onPress={() => this.loginUser()}
title='Login' />
</SafeAreaView>
);
}
}
const StackNav = createStackNavigator(
{
Screen1: Screen1,
Screen2: Screen2
}
);
const CustomDrawerContentComponent = (props) => {
console.log('props in custom component are: ', props);
return (
<ScrollView>
<SafeAreaView style={{ flex: 1 }} forceInset={{ top: 'always', horizontal: 'never' }}>
<Text>Hello {props.screenProps.userId}</Text>
<DrawerItems {...props} />
</SafeAreaView>
</ScrollView>
)
};
const DrawerNav = createDrawerNavigator(
{
StackNav: {
screen: StackNav,
},
SettingScreen: {
screen: SettingScreen
}
},
{
contentComponent: CustomDrawerContentComponent
}
);
class DrawerNavWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedInUser: {
}
};
}
render() {
return(
<DrawerNav screenProps={this.state.loggedInUser} />
);
}
}
export default DrawerNavWrapper;
After "Login" button is pressed in SettingScreen, screenProps is set
this.props.screenProps.userId = 'Gandalf';
and when you open drawer, "Hello Gandalf" will be shown.
The other solution is to use JS Modules.
Or maybe use react context (https://github.com/react-navigation/react-navigation/issues/4511)
Anyone knows a better solution?
Thanks

Need to show Expandable list view inside navigation drawer

I am an Android Application Developer. I have started working on React-Native. I am unable to find a way to show expandable list inside navigation drawer. Suggest a library if this functionality can be done in that. navigationOptions does not have a way to provide a list (refer code below).
I want to show expandable view like item 4
My Code is :-
import {DrawerNavigator} from 'react-navigation';
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
Image,
View,
TouchableHighlight
} from 'react-native';
import Screen1 from './screen/Screen1'
import Screen2 from './screen/Screen2'
const util = require('util');
class MyHomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
headertitle: 'ffffff'
};
}
componentWillReceiveProps(nextProps) {
navigationOptions = {
title: this.nextProps.headertitle
};
}
static navigationOptions = {
drawerLabel: 'Home',
drawerIcon: ({tintColor}) => (
<Image
source={require('./images/document.png')}
style={[
styles.icon, {
tintColor: tintColor
}
]}/>),
title: 'NIIT'
};
render() {
return (<Screen1/>);
}
}
class MyNotificationsScreen extends React.Component {
static navigationOptions = {
drawerLabel: 'Notifications',
drawerIcon: ({tintColor}) => (<Image source={require('./images/smartphone.png')} style={[styles.icon]}/>),
title: 'Gnome'
};
render() {
return (<Screen2/>);
}
}
const styles = StyleSheet.create({
icon: {
width: 24,
height: 24
}
});
const DrawerScreen = DrawerNavigator({
Screen1: {
screen: MyHomeScreen
},
Screen2: {
screen: MyNotificationsScreen
}
}, {headerMode: 'none'})
export default DrawerScreen;
I think this is a single class, simple implementation of what the OP is asking for. It uses react-navigation v5. It's a standalone component that is configured via the ExpandableDrawerProps (title is the name of parent drawer, i.e. what contains the subdrawers and has no navigation, and choices is a map of label name to navigation screen component names.) It is written in TypeScript (both of these are .tsx files), so if you're not using TypeScript, just strip out the typing.
import {
DrawerContentComponentProps,
DrawerContentScrollView,
DrawerItem,
} from '#react-navigation/drawer';
import React from 'react';
import { Text, View } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import styles from './styles';
export type ExpandableDrawerProps = DrawerContentComponentProps & {
title: string;
choices: Map<string, string>;
};
export default class ExpandableDrawer extends React.Component<
ExpandableDrawerProps,
{
isExpanded: boolean;
}
> {
constructor(props: ExpandableDrawerProps, state: { isExpanded: boolean }) {
super(props);
this.state = state;
}
onPress = (): void => {
this.setState(() => {
return {
isExpanded: !this.state.isExpanded,
};
});
};
render = (): JSX.Element => {
return (
<View style={styles.container}>
<TouchableOpacity
activeOpacity={0.8}
onPress={this.onPress}
style={styles.heading}
>
<Text style={styles.expander}>{this.props.title}</Text>
</TouchableOpacity>
{this.state.isExpanded ? (
<DrawerContentScrollView>
<View style={styles.expandedItem}>
{[...this.props.choices.keys()].map(
(label: string): JSX.Element | null => {
const screen = this.props.choices.get(label);
if (screen != undefined) {
return (
<DrawerItem
key={label}
label={label}
onPress={(): void => {
this.props.navigation.navigate(screen);
}}
/>
);
} else {
return null;
}
}
)}
</View>
</DrawerContentScrollView>
) : null}
</View>
);
};
}
You can drop that code in a file, make a simple styles file or remove them from that code, and then you're able to use <ExpandableDrawerMenu {...expandable} />
in your normal drawer navigation.
Here's how I used it in a normal navigation drawer.
const DrawerContent = (props: DrawerContentComponentProps): JSX.Element => {
const c = new Map<string, string>();
c.set('SubItem 1', 'SubItem1');
c.set('SubItem 2', 'SubItem2');
const expandable: ExpandableDrawerProps = {
title: 'Expandable Drawer',
choices: c,
navigation: props.navigation,
state: props.state,
descriptors: props.descriptors,
progress: props.progress,
};
return (
<DrawerContentScrollView {...props}>
<View style={styles.drawerContent}>
<Drawer.Section style={styles.drawerSection}>
<DrawerItem
label="Item 1"
onPress={(): void => {
props.navigation.navigate('Item1');
}}
/>
<ExpandableDrawerMenu {...expandable} />
<DrawerItem>
label="Item 2"
onPress={(): void => {
props.navigation.navigate('Item2');
}}
/>
<DrawerItem
label="Item 3"
onPress={(): void => {
props.navigation.navigate('Item3');
}}
/>
</Drawer.Section>
</View>
</DrawerContentScrollView>
);
};
export default class Navigator extends Component {
render = (): JSX.Element => {
const Drawer = createDrawerNavigator();
return (
<NavigationContainer>
<Drawer.Navigator
drawerContent={(props: DrawerContentComponentProps): JSX.Element =>
DrawerContent(props)
}
initialRouteName="Item1"
>
<Drawer.Screen name="Item1" component={Item1Screen} />
<Drawer.Screen name="SubItem1" component={SubItem1Screen} />
<Drawer.Screen name="SubItem2" component={SubItem2Screen} />
<Drawer.Screen name="Item2" component={Item2Screen} />
<Drawer.Screen name="Item3" component={Item3Screen} />
</Drawer.Navigator>
</NavigationContainer>
);
};
}
react-navigation does not, at this time, support a collapsible menu in the drawer navigator.
You can, however, implement your own, by supplying your own contentComponent to the navigator:
const DrawerScreen = DrawerNavigator({
Screen1: {
screen: MyHomeScreen
},
Screen2: {
screen: MyNotificationsScreen
}
}, {
headerMode: 'none',
contentComponent: MyDrawer
})
const MyDrawer = (props) => ...
See the documentation for more information.
You can use something like react-native-collapsible to achieve the effect of the collapsible menu itself.
I have developed a solution for this problem. My code uses "#react-navigation/drawer": "^5.1.1" and "#react-navigation/native": "^5.0.9".
Gihub link - https://github.com/gyanani-harish/ReactNative-ExpandableDrawerMenu

React Native Navigation const { navigate } = this.props.navigation;

I am learning react-native navigation https://reactnavigation.org/docs/intro/ . I see in examples there
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat')}
title="Chat with Lucy"
/>
</View>
);
}
}
I could not understand what exactly this line of code is for const { navigate } = this.props.navigation;
syntax has nothing to do with React Native
it is called Destructuring assignment in es6 / es2015
const { navigate } = this.props.navigation;
is equivilent to with exception to var and const .
var navigate = this.props.navigation.navigate
the example without Destructuring should look like this
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => this.props.navigation.navigate('Chat')}
title="Chat with Lucy"
/>
</View>
);
}
}
Include on your ServiceAction the this.props.navigation something like this:
<HomeScreen navigation={this.props.navigation}/>
because the props.navigation are by default on your parent component
and on HomeScreen component you will access to navition like:
..
goToSignUp() {
this.props.navigation.navigate('SignUp');
}
..
For me also was confusing before. Cheers!

Sending props with Tabs from 'react-native-tabs'

I am using Tabs from 'react-native-tabs' to navigate from one screen to another, how can I send props to the individual components of the tabs?
I am using the following code to display tabs,
import Tabs from 'react-native-tabs';
<Tabs
selected={page}
style={styles.tabbar}
selectedStyle={{color:'black'}}
onSelect={el=>this.setState({page:el.props.name})}
>
<Text style={styles.tabbarText} name={Contact}>Contact</Text>
<Text style={styles.tabbarText} name={Messages}>Messages</Text>
<Text style={styles.tabbarText} name={Profile}>Profile</Text>
</Tabs>
I figured it out, you can mention the props in the
<this.state.page style={styles.pageContainer} navigator={this.props.navigator}/>
line of code.
The entire render() function is here,
render() {
const { page } = this.state;
return (
<View style={styles.container}>
<this.state.page style={styles.pageContainer} navigator={this.props.navigator}/>
<Tabs
selected={page}
style={styles.tabbar}
selectedStyle={{color:'black'}}
onSelect={el=>this.setState({page:el.props.name})}
>
<Text style={styles.tabbarText} name={Contact}>Contact</Text>
<Text style={styles.tabbarText} name={Messages}>Messages</Text>
<Text style={styles.tabbarText} name={Profile}>Profile</Text>
</Tabs>
</View>
);
}
It may help you.
class Home2Screen extends React.Component {
render() {
console.log(this.props.navigation.state);
return (
<View>
</View>
);
}
}
class SettingsScreen extends React.Component {
render() {
console.log(this.props.navigation.state);
return (
<View>
</View>
);
}
}
const tab = TabNavigator(
{
Home2: { screen: Home2Screen },
Settings: { screen: SettingsScreen },
},
{}
);
class HomeScreen extends React.Component {
goToTab() {
this.props.navigation.navigate('Tab', {
carData: {some: 'data'},
});
}
render() {
return (
<View>
<Button onPress={() => { this.goToTab() }} />
</View>
);
}
}
const HomeNavigator = StackNavigator({
Home: {
screen: HomeScreen,
},
Tab: {
screen: tab,
},
}, {
mode: Platform.OS === 'ios' ? 'modal' : 'card',
});
const HomeNavigationDrawer = DrawerNavigator({
HomePage: {
screen: HomeNavigator,
},
}, {});
export default HomeNavigationDrawer;