React Native TabNavigator, Cannot read property 'navigate' of undefined - javascript

I've just set up a React Navigation TabNavigator which works fine but I'm having issues with transitions between my views.
I get the following error message from trying to redirect the user to another page after a click on a button.
Cannot read property 'navigate' of undefined
I'm super confused on how to make this work.
Here is the navigator:
import React from 'react';
import { TabNavigator, StackNavigator } from 'react-navigation';
import ElemList from './src/components/ElemList';
import ElemShow from './src/components/ElemShow';
const RootTabs = TabNavigator({
Home: {
screen: ElemList,
navigationOptions: {
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-home' : 'ios-home-outline'}
size={26}
style={{ color: tintColor }}
/>
),
},
main: {
screen: StackNavigator({
show: { screen: ElemShow },
})
}
}
});
Here is my code:
onRowPress(data) {
this.props.navigation.navigate('ElemShow', {id: data})
};
render() {
const { key, elem } = this.props;
return (
<TouchableWithoutFeedback onPress={() => this.onRowPress(elem)}>
<View style={styles.viewStyle} key={key}>
<Text>Here is the info</Text>
</View>
</TouchableWithoutFeedback>
)
}

The problem is with this line
this.props.navigation
Should be
this.props.navigator
Additionally I don't know that navigate is part of the screens API; typically you will use push or resetTo.

Related

Expo React Native Drawer Navigator Logout functionality

StackOverflow I am very new to react native since I implement drawer navigation now I want to include a logout button at the end of the drawer but I don't find how to do that any good practice and ideas about how to achieve this kind of functionality. this is my code for drawer I find it from hours of google and it works fine but it has functions of screens I don't find any option of how to make a logout link in this code if this code is not correct then suggest any other good snippet thanks in advance
import React from 'react';
import { Ionicons } from '#expo/vector-icons'
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import { createDrawerNavigator, createStackNavigator, createAppContainer } from 'react-navigation';
import { DrawerActions } from 'react-navigation-drawer';
// _signOutAsync = async () => {
// await AsyncStorage.clear();
// this.props.navigation.navigate('Auth');
// };
const HomeScreen = () => (
<View style={styles.container}>
<Text>Home Screen!</Text>
</View>
);
const ProfileScreen = () => (
<View style={styles.container}>
<Text>Profile Screen!</Text>
</View>
);
const SettingsScreen = () => (
<View style={styles.container}>
<Text>Settings Screen!</Text>
</View>
);
const DrawerNavigator = createDrawerNavigator({
Home: {
screen: HomeScreen,
navigationOptions: ({ navigation }) => ({
title: 'Home Screen',
drawerLabel: 'Home',
drawerIcon: () => (
<Ionicons name="ios-home" size={20} />
)
})
},
Profile: {
screen: ProfileScreen,
navigationOptions: ({ navigation }) => ({
title: 'Profile Screen',
drawerLabel: 'Profile',
drawerIcon: () => (
<Ionicons name="ios-person" size={20} />
)
})
},
Settings: {
screen: SettingsScreen,
navigationOptions: ({ navigation }) => ({
drawerIcon: () => (
<Ionicons name="ios-settings" size={20} />
)
})
},
});
const StackNavigator = createStackNavigator({
DrawerNavigator: {
screen: DrawerNavigator,
navigationOptions: ({ navigation }) => {
const { state } = navigation;
if(state.isDrawerOpen) {
return {
headerLeft: ({titleStyle}) => (
<TouchableOpacity onPress={() => {navigation.dispatch(DrawerActions.toggleDrawer())}}>
<Ionicons name="ios-close" style={styles.menuClose} size={36} color={titleStyle} />
</TouchableOpacity>
),
}
}
else {
return {
headerLeft: ({titleStyle}) => (
<TouchableOpacity onPress={() => {navigation.dispatch(DrawerActions.toggleDrawer())}}>
<Ionicons name="ios-menu" style={styles.menuOpen} size={32} color={titleStyle} />
</TouchableOpacity>
),
}
}
}
}
})
export default createAppContainer(StackNavigator);
You can create one content component to render inside your Drawer Navigator, making it easier to modify.
I will explain with your example (I am assuming that it is your App.js file):
//import CustomDrawer from '...'
const DrawerNavigator = createDrawerNavigator({
Home: {
screen: HomeScreen,
navigationOptions: ({ navigation }) => ({
title: 'Home Screen',
drawerLabel: 'Home',
drawerIcon: () => (
<Ionicons name="ios-home" size={20} />
)
})
},
Profile: {
screen: ProfileScreen,
navigationOptions: ({ navigation }) => ({
title: 'Profile Screen',
drawerLabel: 'Profile',
drawerIcon: () => (
<Ionicons name="ios-person" size={20} />
)
})
},
Settings: {
screen: SettingsScreen,
navigationOptions: ({ navigation }) => ({
drawerIcon: () => (
<Ionicons name="ios-settings" size={20} />
)
})
},
//Here you will add one more pair of curly brackets to add more configs.
}, {
initialRouteName: 'Home',
contentComponent: CustomDrawer //This is the option that will allow you to add the button
});
You will create one entire component to modify like you want and then render inside the Drawer Navigator. Remember, I am using CustomDrawer name, you will import this component inside your App.js file.
import React from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
import { Button } from 'react-native-elements';
import { DrawerNavigatorItems } from 'react-navigation-drawer';
const CustomDrawer = ({ ...props }) => {
return (
<>
<View>
<DrawerNavigatorItems
{...props}
itemsContainerStyle={{}}
itemStyle={{}}
/>
</View>
<View
style={{
flexDirection: 'row',
alignSelf: 'center',
position: 'relative',
marginBottom: 20,
}}
>
<Button
title={'Log out'}
buttonStyle={{ width: 200, borderRadius: 20 }}
onPress={}
/>
</View>
</>
);
};
const styles = StyleSheet.create({});
export default CustomDrawer;
Here I am rendering only the CustomDrawer props, that is the itens that you create in your App.js and rendering it (specifically it is the ...props that I am passing in DrawerNavigationItems, so you can customize it like you want, like one normal screen, place buttons, create views and apply styles to it.
You can also instead of creating one new screen to render inside your Drawer Navigator code it inside your App.js, but personally I feel it much messed up
You can learn more with this tutorial

React Native Pass Param From Drawer Navigator to Tab Navigator

I have a tab navigator inside a drawer navigator and I want to pass data from the draw navigator to a page in the tab navigator.
I tried passing a parameter in the DrawerContainer.js using
this.props.navigation.navigate('TabsNav', {testParam: 'TEST'})
and retrive it by this.props.navigation.getParam in the HomePage screen. But this did not work because the its navigating to the TabNav then rendering the HomePage.
How do I pass data to the tab navigator form the DrawerContainer.js then to the HomePage screen.
TabNav.js
export const Tabs = createMaterialTopTabNavigator(
{
HomePage: {
screen: Home,
},
ListView: {
screen: List,
},
},
{
order: ['HomePage', 'ListView'],
},
)
DrawerContainer.js
render() {
return (
<View style={styles.container}>
<View>
<TouchableHighlight
style={styles.TouchableHighlight}
onPress={this.props.navigation.navigate('TabsNav', {testParam: 'TEST'})}
<Text>Home</Text>
</TouchableHighlight>
</View>
<View>
<TouchableHighlight
style={styles.TouchableHighlight}
onPress={this.props.navigation.navigate('ProfilePage')}>
<Text>List View</Text>
</TouchableHighlight>
</View>
</View>
)
}
}
DrawNav.js
import { Tabs } from './TabNav.js'
import Profile from '../ProfilePage.js'
import DrawerContainer from '../DrawerContainer'
export const Draw = createDrawerNavigator(
{
TabsNav: {screen: Tabs},
ProfilePage: {screen: Profile},
},
{
contentComponent: DrawerContainer
},
);
HomePage.js
export default class HomePage extends Component{
componentWillMount(){
console.log(this.props.navigation.getParam('testParam', null))
}
render(){
<View>
</View>
}
}
you need to navigate to a screen specifically inside of the tab, not to the tab itself, or the params go into the tab navigator. params only go to the route that you are navigating to directly.
so
Drawer({
TabsNav: Tab({
HomePage: ScreenA,
ListView: ScreenB,
})
});
navigate to HomePage or ListView rather than TabsNav
this.props.navigation.navigate('HomePage', { testParam: 'Test' })
Or you can also Dispatch Navigation Actions for this purpose as follow:
import { NavigationActions } from 'react-navigation';
const navigateAction = NavigationActions.navigate({
routeName: 'HomePage',
params: { testParam: 'Test' }
});
this.props.navigation.dispatch(navigateAction);
I hope it help you.

React-Navigation - Header interaction with its screen component, Failed prop type

I am following the React-Navigation tutorial, and got stuck on the section titled Header interaction with its screen component. The code in the tutorial work fine in the emulator provided at snack, but I discovered that when running locally I encountered the following error:
Warning: Failed prop type: The prop 'onPress' is marked as required in 'Button', but its value is 'undefined'.
I managed to get the code working on my local machine using expo-cli by changing the onPress event assignment in navigationOptions as follows (my snack here):
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')} - as in tutorial
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
I am hoping someone might have some insight into why this is so. I checked and I am using the same version of Expo (v.32.0) locally.
App.js listing:
import React from 'react';
import { Button, Image, Platform, View, Text } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
class LogoTitle extends React.Component {
render() {
return (
<Image
source={require('./spiro.png')}
style={{ width: 30, height: 30 }}
/>
);
}
}
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')}
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
),
};
};
componentWillMount() {
this.props.navigation.setParams({ increaseCount: this._increaseCount });
}
state = {
count: 0,
};
_increaseCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Text>Count: {this.state.count}</Text>
<Button
title="Go to Details"
onPress={() => {
/* 1. Navigate to the Details route with params */
this.props.navigation.navigate('Details', {
itemId: 86,
otherParam: 'First Details',
});
}}
/>
</View>
);
}
}
class DetailsScreen extends React.Component {
static navigationOptions = ({ navigation, navigationOptions }) => {
const { params } = navigation.state;
return {
title: params ? params.otherParam : 'A Nested Details Screen',
/* These values are used instead of the shared configuration! */
headerStyle: {
backgroundColor: navigationOptions.headerTintColor,
},
headerTintColor: navigationOptions.headerStyle.backgroundColor,
};
};
render() {
/* 2. Read the params from the navigation state */
const { params } = this.props.navigation.state;
const itemId = params ? params.itemId : null;
const otherParam = params ? params.otherParam : null;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Text>otherParam: {JSON.stringify(otherParam)}</Text>
<Button
title="Update the title"
onPress={() =>
this.props.navigation.setParams({ otherParam: 'Updated!' })
}
/>
<Button
title="Go to Details... again"
onPress={() => this.props.navigation.navigate('Details')}
/>
<Button
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
const RootStack = createStackNavigator(
{
Home: {
screen: HomeScreen,
},
Details: {
screen: DetailsScreen,
},
},
{
initialRouteName: 'Home',
defaultNavigationOptions: {
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
}
);
const AppContainer = createAppContainer(RootStack);
export default class App extends React.Component {
render() {
return <AppContainer />;
}
}
My guess is that this is not a fatal error, just a warning.
It will happen in any case. React Navigation docs state:
React Navigation doesn't guarantee that your screen component will be mounted before the header. Because the increaseCount param is set in componentDidMount, we may not have it available to us in navigationOptions. This usually will not be a problem because onPress for Button and Touchable components will do nothing if the callback is null. If you have your own custom component here, you should make sure it behaves as expected with null for its press handler prop.
So, navigationOptions function will be called twice:
First time before componentDidMount. Here, getParam will return undefined.
Second time after componentDidMount.
What Button is complaining about, is the first time. It does not like onPress set to undefined.
You can check this with console.log from navigationOptions:
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
console.log(navigation.getParam('increaseCount'))
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')}
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
),
};
};
In my opinion, your code is correct, while the code from the docs simply ignores this issue.
Try instead of navigation.getParam() to use navigation.navigate()

Combine createStackNavigator and createBottomTabNavigator?

Scenario :
I have an app working with three tabs for navigation (School, Admin, Family);
I am now trying to add in other screens, that will not have corresponding tabs. These screens will be navigated to using something like this.props.navigation.navigate('ChatScreen')
Issue
- With my past solution, any time I added a screen it would add a tab for that screen.
Todo :
I would like to have the tabs in my navigation stack, as well as other normal (not-tab) screens.
I would like both the tabs and the header to be persistent
I have been unsuccessful at combining both, and have tried many
variations of the code below.
Tried Code :
const School = createStackNavigator({
School: {
screen: SchoolScreen,
navigationOptions: {
headerTitle: <CustomHeaderTitle screen='school'/>
}
}
});
const Admin = createStackNavigator(
{ Admin: {
screen: AdminScreen,
navigationOptions: {
headerTitle: <CustomHeaderTitle screen='admin' />
}
}
});
const Family = createStackNavigator(
{
Family: {
screen: FamilyScreen,
navigationOptions: {
headerLeft: null,
headerTitle: <CustomHeaderTitle screen='family' />
}
}
}
);
const ChatStack = createStackNavigator({
CreateChat: CreateChatScreen
});
const TabStack = createBottomTabNavigator(
{
School: School,
Admin: Admin,
Family: Family
},
{
navigationOptions: ({ navigation }) => ({
tabBarIcon: () => {
const { routeName } = navigation.state;
return <Image id={1} source={require('./app/img/school_logo.png')} />
},
tabBarLabel: navigation.state.routeName
}),
tabBarOptions: {
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
style: {
backgroundColor: 'black',
height: 55
}
}
}
);
const RootStack = createStackNavigator({
Root: ChatStack,
Tabs: TabStack
})
export default class App extends Component {
render() { return (
<Provider store={store}>
<RootStack />
</Provider>
);
}
}
I apologize, I cannot get this code to format after fighting with it for some time.
Thank you for any help or recommendations in advance!
Please suggest.
Credit to an unnamed redditor:
You'll have to nest you're entire stack into each screen of the tab navigator. As far as I know you can't access different screens in a StackNavigator if they are nested inside a different TabNavigator screen.
For example, if you want to be able to navigate to the chat screen from the SchoolScreen, you'll have to include that component inside your School navigator.
const School = createStackNavigation({
School: {
screen: SchoolScreen
},
SchoolChat: {
screen: CreateChatScreen
}
});
From there your main TabNavigator should look about the same
const TabStack = createBottomTabNavigator({
School: School
});
you should hide the RootStack header when TabStack is focused
TabStack.navigationOptions = {
// Hide the header from root stack
header: null,
};
and you did not need add stack to CreateChatScreen
const RootStack = createStackNavigator({
Tabs: TabStack,
ChatScreen: CreateChatScreen,
})
In react native navigation 5
import React from 'react';
import {Text} from 'react-native';
import {createStackNavigator} from '#react-navigation/stack';
import {NavigationContainer} from '#react-navigation/native';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
function Scr(){
return <Text>hello</Text>;
}
function MyTabs() {
return (
<Tab.Navigator
initialRouteName="Expolre"
tabBarOptions={{
activeTintColor: '#414757',
}}>
<Tab.Screen name="Expolre" component={Scr} />
</Tab.Navigator>
);
}
export default function Routing() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="login"
component={Scr}
options={{header: () => null}}
/>
<Stack.Screen
name="dashboard"
component={MyTabs}
options={{header: () => null}}
/>
</Stack.Navigator>
</NavigationContainer>
);
}

Custom close button inside the header of StackNavigator of React Navigation

I'm currently working with React Native and there I've got a question about React Navigation and the StackNavigator. I would like to add a custom close button to the header, but I'm not sure how to do this.
It's just possible for me to navigate to the screens, but not for example to go back or dispatch something.
On my example the navigationOptions for the HomeScreen is working. The drawer opens and the SettingsButton navigates to the settings screen. But I've got problems with the navigationOptions of the EntryScreen. There I build a CloseButton to go back, but I need access to the navigation object.
With navigate it's possible to navigate to other screens, but I need access to the parent object navigation. For example on the class for the drawer it's possible to get access through props.navigation (For example props.navigation.navigate('Home') or props.navigation.goBack(null))
This is a part of my current code. Of course it's currently not working because of the missing access to navigation:
const Stack = StackNavigator({
Home: {
screen: HomeScreen,
navigationOptions: {
header: ({ navigate }) => ({
left: <DrawerButton navigate={navigate} />,
right: <SettingsButton navigate={navigate} />
})
}
},
Entry: {
screen: EntryScreen,
navigationOptions: {
header: ({ navigation }) => ({
right: <CloseButton navigate={navigation } />
})
}
}
})
export const Drawer = DrawerNavigator({
Home: {
screen: Stack
}},
{
contentComponent: HamburgerMenu
}
)
These are the buttons:
export const CloseButton = (props) => {
let testButton = <TouchableHighlight onPress={() => props.navigation.goBack(null)}>
<Icon name='close' style={styles.headerButtonIcon} />
</TouchableHighlight>
return testButton
}
export const SettingsButton = (props) => (
<TouchableHighlight onPress={() => props.navigate('Settings')}>
<Icon name='more-vert' style={styles.headerButtonIcon} />
</TouchableHighlight>
)
Maybe you can help me there. Thanks in advance!
I've found the answer thanks to the help of sigmazen on Github.
Instead of navigation I have to put goBack into the header for the CloseButton. After this I'm able to use it through props.goBack(null)
Entry: {
screen: EntryScreen,
navigationOptions: {
title: `Eintrag`,
header: ({ goBack }) => ({
right: <CloseButton goBack={goBack} />
})
}
},
export const CloseButton = (props) => {
let test = <TouchableHighlight onPress={() => props.goBack(null)}>
<Icon name='close' style={styles.headerButtonIcon} />
</TouchableHighlight>
return test
}

Categories