I'm very new to React Native, right now I'm trying to build a Login component, once login succeed, I'd like to redirect the component from Login screen to the main screen. I'm using React Navigation and somehow it keeps showing me the error "Cannot read property 'navigate' of undefined".
Did I do something wrong or I just missed something? Very appreciate for any kind help!
I'm using Visual Studio Code-Insiders (v1.37.0)
npm -v:6.4.1
expo -V:2.21.2
react-navigation: ^3.11.0
The code very basic:
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Avatar, Button, Icon } from 'react-native-elements'
import * as t from 'tcomb-form-native'
import Router from '../config/router';
const Form = t.form.Form;
const PWD = '2';
const UME = 'J';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
login: {}
};
this.LoginModel = t.struct({
un: t.String,
pwd: t.String,
rm: t.Boolean
});
this.LoginOptions = {
fields: {
un: {
label: 'User Name',
error: 'Please type your username'
},
pwd: {
label: 'Password',
password: true,
secureTextEntry: true,
error: 'Please type the password'
},
rm: {
label: 'Remember me'
}
}
}
}
onChange(form) {
this.state.login = form;
}
onSubmit() {
console.log(this.props); // will output an empty object! but how?
const { navigate } = this.props.navigation; // Here is where the error message captured and throw out!
const value = this.refs.form.getValue();
if (value.un === UME && value.pwd === PWD) {
alert('Welcome back, Jack');
// setTimeout(() => {
// navigate('Home');
// }, 300);
}
}
render() {
return (
<View style={styles.container}>
<View style={{ alignItems: "center", marginBottom: 30 }}>
<Avatar
size="xlarge"
icon={{ name: 'apple', color: 'black', type: 'font-awesome' }}
overlayContainerStyle={{ backgroundColor: 'white' }}
onPress={() => console.log("Works!")}
activeOpacity={0.7}
containerStyle={{ marginTop: 15 }}
/>
</View>
<Form
ref="form"
type={this.LoginModel}
value={this.state.login}
options={this.LoginOptions}
onChange={(f) => this.onChange(f)}
/>
<Button
icon={
<Icon
type='font-awesome'
name="user"
size={25}
color="white"
iconStyle={styles.icon}
/>
}
title="Login"
style={styles.btn}
onPress={()=>this.onSubmit()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 80
},
icon: {
paddingRight: 10
},
btn: {
}
});
export default Login;
UPDATE:
This is the App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import LoginScreen from './src/screens/Login'
export default function App() {
return (
<LoginScreen />
);
}
Based on your code, you have yet created the navigation stack, just add a StackNavigator for your Login component should work
// Other imports
import { createStackNavigator, createAppContainer } from "react-navigation";
...
class Login extends Component {
....
}
const styles = StyleSheet.create({...});
const AppNavigator = createStackNavigator({
Login: {
screen: Login
}
});
export default createAppContainer(AppNavigator);
You can refer the react-navigation's document
However this is just a quick way of doing it, you may need to structure the navigation stack in separate file.
Using a arrow function might work:
onSubmit = () => {
console.log(this.props);
const { navigate } = this.props.navigation;
//...
}
The keyword 'this' refers to the function that calls it. But onSubmit has no props.
"ES5 introduced the bind() method to set the value of a function this regardless of how it's called, and ES2015 introduced arrow functions which don't provide their own this binding (it retains the this value of the enclosing lexical context)." You can read more about that in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
Always prefer using arrow functions to avoid this. :)
From the code i would assume that your are rendering LoginScreen inside your App.js.
Doing that, you are not putting LoginScreen inside any navigator, making this.props.navigation equal to undefined.
Related
I have created the header using defaultNavigationOptions The navigation bar contains Home, signup, login, create blog options. If the user has signed in then Login option should not be visible, instead, logout option should be enabled. I'm using AsyncStorage to store the token.
App.js:
import React from 'react';
import { CreateBlog } from './app/views/CreateBlog.js';
import { BlogDetails } from './app/views/BlogDetails.js';
import { EditBlog } from './app/views/EditBlog.js';
import { DeleteBlog } from './app/views/DeleteBlog.js';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import { Home } from './app/views/Home.js';
import { Login } from './app/views/Login.js';
import {
StyleSheet, Text, View, TextInput, TouchableHighlight, Alert, AsyncStorage
} from 'react-native';
import { Signup } from './app/views/Signup.js';
const AppNavigator = createStackNavigator(
{
CreateBlogRT: {
screen: CreateBlog
},
BlogDetailsRT: {
screen: BlogDetails
},
EditBlogRT: {
screen: EditBlog
},
DeleteBlogRT: {
screen: DeleteBlog
},
SignupRT: {
screen: Signup
},
LoginRT: {
screen: Login
},
HomeRT: {
screen: Home,
},
},
{
initialRouteName: 'HomeRT',
defaultNavigationOptions: ({ navigation }) => ({
header: <View style={{
flexDirection: "row",
height: 80,
backgroundColor: '#f4511e', fontWeight: 'bold'
}} >
<TouchableHighlight onPress={() => navigation.navigate('SignupRT')}>
<Text > SignUp</Text>
</TouchableHighlight>
<TouchableHighlight onPress={() => navigation.navigate('CreateChapterRT')}>
<Text > CreateChapter</Text>
</TouchableHighlight>
<TouchableHighlight onPress={() => navigation.navigate('LoginRT')}>
<Text > Login</Text>
</TouchableHighlight>
//getting 'TypeError: Cannot read property 'state' of undefined'
{this.state.logged_in &&
<TouchableHighlight onPress={async () => {
await AsyncStorage.removeItem('token');
await AsyncStorage.removeItem('user_id');
}}>
<Text > Logout</Text>
</TouchableHighlight>
}
</View >
}),
},
}
);
const MyRoutes = createAppContainer(AppNavigator);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { logged_in: false };
}
componentDidMount = async () => {
var logged_in = await AsyncStorage.getItem('token')
this.setState({ logged_in: false })
}
render() {
return (
<MyRoutes />
);
}
}
Now on clicking the logout button, it deletes the token. I want the logout button should be visible only if the user has logged in. In the same way login & signup options as well. (
const loggedin=AsyncStorage.getItem('token');if(userloggedin)
{showlogout:true;showlogin:false;showSignup:false}.
I don't know where to write the code. Thanks in advance.
after sign-in user you can set userloggedin true
AsyncStorage.setItem('userloggedin',JSON.stringfy(true)
or
AsyncStorage.setItem('userloggedin',Boolean(true))
then getItem
const logged-in = AsyncStorage.getItem('userloggedin')
if(loggedin){
showlogout=true
showlogin=false
showSignup=false
or if your are using state
this.setState({
showlogout:true,
showlogin:false,
showSignup:false})
}.
you can use react life-cycle for this work and also use global variable or state , like this
check this image
https://camo.githubusercontent.com/3beddc08b0e4b44fbdcef20809484f9044e091a2/68747470733a2f2f63646e2d696d616765732d312e6d656469756d2e636f6d2f6d61782f323430302f312a736e2d66746f7770305f565652626555414645434d412e706e67
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {logged_in: false};
}
componentDidMount=async() {
var logged_in = await AsyncStorage.getItem('token')
this.setState({logged_in: false})
}
render() {
const {logged_in} = this.state
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {logged_in}.</h2>
</div>
);
}
}
The function I am not able to run is the navigation functions in my example it's
this.this.props.navigation.goBack()
My Login File is posted below but the part with the problem is the short snippet
The error I am getting is: TypeError: undefined is not an object (evaluating 'LogIn.props.navigation')
First failed Snippet
static navigationOptions = {
headerLeft: () => (
<Button
onPress={()=>this.props.navigation.goBack()}
title="cancel"
color={colors.black}
/>
),
};
LogIn.js
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import Icon from 'react-native-vector-icons/FontAwesome';
import colors from '../styles/colors';
import {
View,
Text,
ScrollView,
StyleSheet,
KeyboardAvoidingView,
Button
} from 'react-native';
import InputField from '../components/form/InputField';
import NexArrowButton from '../components/buttons/NextArrowButton';
import Notification from '../components/Notification';
export default class LogIn extends Component{
constructor(props){
super(props);
this.state ={
formValid:false,
validEmail:false,
emailAddress:'',
validPassword:false,
}
this.handleNextButton = this.handleNextButton.bind(this)
this.handleCloseNotification = this.handleCloseNotification.bind(this)
this.handleEmailChange = this.handleEmailChange.bind(this);
}
static navigationOptions = {
headerLeft: () => (
<Button
onPress={()=>this.props.navigation.goBack()}
title="cancel"
color={colors.black}
/>
),
};
handleNextButton(){
if(this.state.emailAddress === 'admin#mail.com'){
this.setState({formValid:true})
} else{
this.setState({formValid: false});
}
}
handleCloseNotification(){
this.setState({formValid:true });
}
handleEmailChange(email){
const emailCheckRegex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const { validEmail } = this.state;
this.setState({ emailAddress: email });
if (!validEmail) {
if (emailCheckRegex.test(email)) {
this.setState({ validEmail: true });
}
} else if (!emailCheckRegex.test(email)) {
this.setState({ validEmail: false });
}
}
handlePasswordChange(password){
const { validPassword } = this.state;
this.setState({ password });
if (!validPassword) {
if (password.length > 4) {
// Password has to be at least 4 characters long
this.setState({ validPassword: true });
}
} else if (password <= 4) {
this.setState({ validPassword: false });
}
}
render(){
const {formValid, validPassword} = this.state;
const showNotification = formValid ? false:true;
const background = formValid ? colors.green01 : colors.darkOrange;
const notificationMarginTop = showNotification ? 10:0;
return(
<KeyboardAvoidingView style={[{backgroundColor:background}, styles.wrapper] } behavior="padding">
<View style={styles.ScrollViewWrapper}>
<ScrollView style={styles.ScrollView}>
<Text style={styles.loginHeader}>Log In</Text>
<InputField
labelText= "Email Address"
labelTextSize={20}
labelColor={colors.white}
textColor={colors.white}
borderBottomColor={colors.white}
inputType="email"
customStyle={{marginBottom:30}}
onChangeText={this.handleEmailChange}
/>
<InputField
labelText= "Password"
labelTextSize={20}
labelColor={colors.white}
textColor={colors.white}
borderBottomColor={colors.white}
inputType="password"
customStyle={{marginBottom:30}}
/>
</ScrollView>
<View style={styles.nextButton}>
<NexArrowButton
// handleNextButton={this.handleNextButton}
handleNextButton={()=>this.props.navigation.goBack()}
/>
</View>
<View style={[styles.notificationWrapper, {marginTop:notificationMarginTop}]}>
<Notification
showNotification={showNotification}
handleCloseNotification={this.handleCloseNotification}
type="Error"
firstLine="Those credentials don't look right."
secondLine="Please try again."
/>
</View>
</View>
</KeyboardAvoidingView>
)
}
}
const styles = StyleSheet.create({
wrapper:{
display:'flex',
flex:1,
},
ScrollViewWrapper:{
marginTop:60,
flex:1,
},
ScrollView:{
paddingLeft:30,
paddingRight:30,
paddingTop:10,
flex:1,
},
loginHeader:{
fontSize:34,
color:colors.white,
fontWeight:'300',
marginBottom:40,
},
nextButton:{
position:'absolute',
right:20,
bottom:20,
},
notificationWrapper:{
position: 'absolute',
bottom:0,
zIndex:9
}
});
The most confusing part is that the second snippet below of Login.js works perfectly and it is essentially the same thing which means that I am getting the props right, but still get the error in customised back button.
Second working snippet
<View style={styles.nextButton}>
<NexArrowButton
// handleNextButton={this.handleNextButton}
handleNextButton={()=>this.props.navigation.goBack()}
/>
</View>
App.js
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
import LoggedOut from './src/screens/LoggedOut';
import LogIn from './src/screens/LogIn';
import LoggedIn from './src/screens/LoggedIn';
import {createAppContainer} from 'react-navigation';
import {createStackNavigator} from 'react-navigation-stack';
const RootStack = createStackNavigator(
{
LoggedOut: LoggedOut,
LogIn: LogIn,
},
{
initialRouteName: 'LoggedOut',
}
);
const AppContainer = createAppContainer(RootStack);
export default class App extends React.Component {
render() {
return <AppContainer />;
}
}
The error in more details
I really appreciate your help ! I am happy to provide more code if it makes it easier to debugg.
You have to change the static navigationOptions to following snippet if you want to access navigation properties in a static function:
static navigationOptions = ({ navigation }) => ({
headerLeft: () => (
<Button
onPress={()=>navigation.goBack()}
title="cancel"
color={colors.black}
/>
),
});
You don't need the this.props in this case ;) The static function does not have access to the this context so this.props will not work.
Im new to react native and I'm stuck at following.
Im performing navigation (when clicked on alert view button) using the code below.
const {navigation} = this.props.navigation;
…
.
.
{ text: 'Done', onPress:() => {
navigate.push(HomeScreen);}
How can I pass data to another Page in React native? Can I declare the parameter global and just assign to it?
What would be the correct way of performing this and how would I go about it?
Note
This answer was written for react-navigation: "3.3.0". As there are newer versions available, which could bring changes, you should make sure that you check with the actual documentation.
Passing data between pages in react-navigation is fairly straight forward. It is clearly explained in the documentation here
For completeness let's create a small app that allows us to navigate from one screen to another passing values between the screens. We will just be passing strings in this example but it would be possible to pass numbers, objects and arrays.
App.js
import React, {Component} from 'react';
import AppContainer from './MainNavigation';
export default class App extends React.Component {
render() {
return (
<AppContainer />
)
}
}
MainNavigation.js
import Screen1 from './Screen1';
import Screen2 from './Screen2';
import { createStackNavigator, createAppContainer } from 'react-navigation';
const screens = {
Screen1: {
screen: Screen1
},
Screen2: {
screen: Screen2
}
}
const config = {
headerMode: 'none',
initialRouteName: 'Screen1'
}
const MainNavigator = createStackNavigator(screens,config);
export default createAppContainer(MainNavigator);
Screen1.js and Screen2.js
import React, {Component} from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
export default class Screen extends React.Component {
render() {
return (
<View style={styles.container}>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
}
});
Here we have 4 files. The App.js which we will import the MainNavigation.js. The MainNavigation.js sets up a StackNavigator with two screens, Screen1.js and Screen2.js. Screen1 has been set as the initial screen for our StackNavigator.
Navigating between screens
We can navigate from Screen1 to Screen2 by using
this.props.navigation.navigate('Screen2');
and we can go back to Screen1 from Screen2 by using
this.props.navigation.goBack();
So code in Screen1 becomes
export default class Screen extends React.Component {
render() {
return (
<View style={styles.container}>
<Button title={'Go to screen 2'} onPress={() => this.props.navigation.navigate('Screen2')} />
</View>
)
}
}
And code in Screen2 becomes:
export default class Screen extends React.Component {
render() {
return (
<View style={styles.container}>
<Button title={'Go back'} onPress={() => this.props.navigation.goBack()} />
</View>
)
}
}
Now we can navigate between Screen1 and Screen2
Sending values from Screen1 to Screen2
To send a value between Screen1 and Screen2, two steps are involved. First we have to send it, secondly we have to capture it.
We can send a value by passing it as a second parameter. Notice how the text value is contained in an object.
this.props.navigation.navigate('Screen2', {text: 'Hello from Screen 1' });
And we can capture it in Screen2 by doing the following, the first value in getParams is the key the second value is the default value.
const text = this.props.navigation.getParams('text','nothing sent');
So Screen1 now becomes
export default class Screen extends React.Component {
render() {
return (
<View style={styles.container}>
<Button
title={'Go to screen 2'}
onPress={() => this.props.navigation.navigate('Screen2', {
text: 'Hello from screen 1'
})} />
</View>
)
}
}
And code in Screen2 becomes:
export default class Screen extends React.Component {
render() {
const text = this.props.navigation.getParam('text', 'nothing sent')
return (
<View style={styles.container}>
<Text>{text}</Text>
<Button
title={'Go back'}
onPress={() => this.props.navigation.goBack()} />
</View>
)
}
}
Sending values from Screen2 back to Screen1
The easiest way I have discovered to send a value from Screen2 to Screen1 is to pass a function to Screen2 from Screen1 that will update the state in Screen1 with the value that you want to send
So we can update Screen1 to look like this. First we set an initial value in state. Then we create a function that will update the state. Then we pass that function as a parameter. We will display the captured value from Screen2 in a Text component.
export default class Screen1 extends React.Component {
state = {
value: ''
}
receivedValue = (value) => {
this.setState({value})
}
render() {
return (
<View style={styles.container}>
<Button
title={'Go to screen 2'}
onPress={() => this.props.navigation.navigate('Screen2', {
text: 'Hello from Screen 1',
receivedValue: this.receivedValue }
)} />
<Text>{this.state.value}</Text>
</View>
)
}
}
Notice that we are passing the function receivedValue in the same way that we passed the text earlier.
Now we have to capture the value in Screen2 and we do that in a very similar way that we did previously. We use getParam to get the value, remembering to set our default. Then when we press our Go back button we update it to call the receivedValue function first, passing in the text that we want to send back.
export default class Screen2 extends React.Component {
render () {
const text = this.props.navigation.getParam('text', 'nothing sent');
const receivedValue = this.props.navigation.getParam('receivedValue', () => {});
return (
<View style={styles.container}>
<Button
title={'Go back'}
onPress={() => {
receivedValue('Hello from screen 2')
this.props.navigation.goBack()
}} />
<Text>{text}</Text>
</View>
);
}
}
Alternatives to using getParam
It is possible to not use the getParam method and instead access the values directly. If we were to do that we would not have the option of setting a default value. However it can be done.
In Screen2 we could have done the following:
const text = this.props.navigation.state.params.text;
const receivedValue = this.props.navigation.state.params.receivedValue;
Capturing values in lifecycle events (Screen1 to Screen2)
react-navigation allows you to capture values using the lifecycle events. There are a couple of ways that we can do this. We could use NavigationEvents or we could use listeners set in the componentDidMount
Here is how to set it up using NavigationEvents
import React, {Component} from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { NavigationEvents } from 'react-navigation'; // you must import this
export default class Screen2 extends React.Component {
state = {
text: 'nothing passed'
}
willFocusAction = (payload) => {
let params = payload.state.params;
if (params && params.value) {
this.setState({value: params.value});
}
}
render() {
return (
<View style={styles.container}>
<NavigationEvents
onWillFocus={this.willFocusAction}
/>
<Text>Screen 2</Text>
<Text>{this.state.text}</Text>
</View>
)
}
}
Here is how to do it using listeners in the componentDidMount
export default class Screen2 extends React.Component {
componentDidMount () {
// we add the listener here
this.willFocusSubscription = this.props.navigation.addListener('willFocus', this.willFocusAction);
}
componentWillUmount () {
// we remove the listener here
this.willFocusSubscription.remove()
}
state = {
text: 'nothing passed'
}
willFocusAction = (payload) => {
let params = payload.state.params;
if (params && params.value) {
this.setState({value: params.value});
}
}
render() {
return (
<View style={styles.container}>
<Text>Screen 2</Text>
<Text>{this.state.text}</Text>
</View>
)
}
}
Passing navigation via components
In the above examples we have passed values from screen to screen. Sometimes we have a component on the screen and we may want to navigate from that. As long as the component is used within a screen that is part of a navigator then we can do it.
If we start from our initial template and construct two buttons. One will be a functional component the other a React component.
MyButton.js
// this is a functional component
import React, {Component} from 'react';
import { View, StyleSheet, Text, TouchableOpacity } from 'react-native';
export const MyButton = ({navigation, value, title}) => {
return (
<TouchableOpacity onPress={() => navigation.navigate('Screen2', { value })}>
<View style={styles.buttonStyle}>
<Text>{title}</Text>
</View>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
buttonStyle: {
width: 200,
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'red'
}
});
MyOtherButton.js
// this is a React component
import React, {Component} from 'react';
import { View, StyleSheet, Text, TouchableOpacity } from 'react-native';
export default class MyOtherButton extends React.Component {
render() {
const { navigation, value, title } = this.props;
return (
<TouchableOpacity onPress={() => navigation.navigate('Screen2', { value })}>
<View style={styles.buttonStyle}>
<Text>{title}</Text>
</View>
</TouchableOpacity>
)
}
}
const styles = StyleSheet.create({
buttonStyle: {
width: 200,
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'yellow'
}
});
Regardless of the type of component, notice that navigation is a prop. We must pass navigation to the component otherwise it will not work.
Screen1.js
import React, {Component} from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import { MyButton } from './MyButton';
import MyOtherButton from './MyOtherButton';
export default class Screen1 extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Screen 1</Text>
<MyButton
title={'Press my button'}
navigation={this.props.navigation}
value={'this is a string passed using MyButton'}
/>
<MyOtherButton
title={'Press my other button'}
navigation={this.props.navigation}
value={'this is a string passed using MyOtherButton'}
/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
}
});
Notice in Screen1.js as it is contained in a StackNavigator it will have access to this.props.navigation. We can pass that through to our component as a prop. As long as we use that in our component then we should be able to navigate by using the components own functionality.
<MyButton
title={'Press my button'}
navigation={this.props.navigation} // pass the navigation here
value={'this is a string passed using MyButton'}
/>
Snacks
Here is a snack for passing params.
Here is a snack for passing params and capturing in lifecycle events.
Here is a snack passing navigation to components
1) On Home Screen:-
Initialise:-
constructor(props) {
super(props);
this.navigate = this.props.navigation.navigate; }
Send:-
this.navigate("DetailScreen", {
name: "Detail Screen",
about:"This is Details Screen Page"
});
2) On Detail Screen:-
Initialise:-
constructor(props) {
super(props);
this.params = this.props.navigation.state.params;
}
Retrive data:-
console.log(this.params.name);
console.log(this.params.about);
const {navigate} = this.props.navigation;
…
.
.
{ text: 'Done', onPress:() => {
navigate('homeScreen',...params);}
You can get those params like
const {params} = this.props.navigation.state
HomeScreen.js
this.props.navigation.navigate('Screen2',{ user_name: 'aaa',room_id:'100' });
Screen2.js
const params = this.props.route.params;
user_name = params.user_name;
room_id = params.room_id
You can easily send and receive your params with react-navigation like below
Send params:
{
text: 'Done',
onPress: () => {
this.props.navigation.navigate(
HomeScreen,
{param1: 'value1', param2: 'value2'}
);
}
}
Get params in HomeScreen:
const { navigation } = this.props;
var param1 = navigation.getParam('param1', 'NO-VALUE');
var param2 = navigation.getParam('param2', 'NO-VALUE');
the 'NO-VALUE' is default value, if there is not desired param
I am assuming that you are using react-navigation. So, in react-navigation we can pass data in two pieces:
Pass params to a route by putting them in an object as a second parameter to the navigation.navigate function:
this.props.navigation.navigate('RouteName', { /* params go here */ })
Read the params in your screen component:
this.props.navigation.getParam(paramName, someDefaultValue)
Alert Button
<Button
title="Alert View"
onPress={() => {
this.props.navigation.navigate('alerts', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/>
Screen:
const itemId = navigation.getParam('itemId', 'NO-ID');
const otherParam = navigation.getParam('otherParam', 'some default value')
Screen 1:
<Button title="Go Next"
onPress={() => navigation.navigate('SecondPage', { paramKey: userName })} />
Screen 2:
const SecondPage = ({route}) => {
....
....
<Text style={styles.textStyle}>
Values passed from First page: {route.params.paramKey}
</Text>
....
....
}
Im doing this inside the react native platform using expo.
I want to display the list of items ( ListItems.js) All_Employees_screen.js . These items are being rendered via a functional component, I want to have a onRowPress() handler to so that upon clicking it i can navigate it to another view, but I dont know how to do it on react-navigation ?
Or since the new functional component can be a class component( this would be better ) how can i access the navigation thing inside it ?
AllProperties.js
import _ from 'lodash';
import React, {
Component
} from 'react';
import {
Button,
ListView,
ScrollView
} from 'react-native';
import ListItem from './ListItem';
import { connect } from 'react-redux';
import { propertiesFetch } from '../../actions';
// import { FormLabel, FormInput } from 'react-native-elements'
class AllPropertiesScreen extends React.Component {
componentWillMount(){
this.props.propertiesFetch();
this.createDataSource(this.props);
}
// we do this componentWillMount & componentWillReceiveProps (nextProps) thing twice, coz once the component is
// loaded it loads all teh values but when user hits another view like Create property, The Property data still exists
// in the global state object,
// we could move all the dc dataSource code into componentWillReceiveProps but its actually gonna benefit us
// if we make sure that we try to build our data source both when the component first loads up
// & when second time after we go back and forth other compoennts.
componentWillReceiveProps(nextProps){
// nextProps are the next set of props that this component will be rendered with
// this.props is still the old set of props
this.createDataSource(nextProps);
}
createDataSource({ properties }){
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(properties);
}
static navigationOptions = ({ navigation }) => {
const {state, setParams} = navigation;
return {
title: 'All Emplooyee',
headerRight: (
<Button
title='Add'
// onPress={() => setParams({ mode: isInfo ? 'none' : 'info'})}
onPress={() => navigation.navigate('createProperty')
}
/>
),
};
};
goBack(){
console.log('65 - go Back clicked');
}
renderRow(property){
// console.log('67-AllPropertiesScreen =', property);
return <ListItem property={property}
onPress={() => { console.log('65 - go Back clicked') }}
/>;
}
render() {
console.log('72-AllPropertiesScreen this.props', this.props );
return(
<ListView
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
);
}
}
const mapStateToProps = state => {
console.log('83 - AllPropertiesScreen state. properties', state );
const properties = _.map(state.properties, (val, uid ) => {
return { ...val, uid }; // { shift: 'Monday'}
});
return { properties };
};
export default connect(mapStateToProps, {propertiesFetch}) (AllPropertiesScreen);
ListItem.js
import React, { Component } from 'react';
import { Text, TouchableWithoutFeedback, View } from 'react-native';
class ListItem extends Component {
// onRowPress(){
// Actions.employeeEdit({ employee: this.props.employee });
// }
render(){
const { agent_name, cell, address } = this.props.property;
console.log('14- ListItem ', this.props);
return (
<View>
<CardSection>
<Text style={styles.titleStyle}>
name
</Text>
<Text style={styles.titleStyle}>
cell
</Text>
<Text style={styles.titleStyle}>
address
</Text>
</CardSection>
</View>
);
}
}
const styles = {
titleStyle: {
fontSize: 18,
paddingLeft: 15
}
}
export default ListItem;
//
main.js ( this is where I have all the navigation paths hookedup.
class App extends React.Component {
render() {
const MainNavigator = TabNavigator({
// auth: { screen : AuthScreen },
// review: { screen: ReviewScreen },
// signup: { screen : SignupScreen },
followup: { screen: FollowupScreen }, welcome: { screen : WelcomeScreen },
auth: { screen : AuthScreen },
signup: { screen : SignupScreen },
main: {
screen: TabNavigator ({
followup: { screen: FollowupScreen },
map: { screen: MapScreen },
deck: { screen: DeckScreen },
settings : {
screen: StackNavigator ({
settings: { screen: SettingsScreen },
// settings: { screen: SettingsScreen },
UserProfile: { screen: UserProfileScreen },
HelpSupport: { screen: HelpSupportScreen },
Notifications: { screen: NotificationsScreen },
Signout: { screen: SignoutScreen } // not working, Navigation object not accessible inside the component
}) //screen: StackNavigator ({
},
followup : {
screen: StackNavigator ({
followup: { screen: FollowupScreen },
allProperties: { screen: AllPropertiesScreen },
createProperty: { screen: PropertyCreateScreen },
Red: { screen: RedPriorityScreen }, // not working, Navigation object not accessible inside the component
GreyPriority: { screen: GreyPriorityScreen },
}) //screen: StackNavigator ({
},
draw: {
screen: DrawerNavigator ({
drawin: { screen: DrawScreen },
}) //screen: StackNavigator ({
}
}) //screen: TabNavigator
}
}, {
navigationOptions: {
tabBarVisible: false
},
lazy: true
});
return (
<Provider store={store}>
<View style={styles.container}>
<MainNavigator />
</View>
</Provider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
// alignItems: 'center',
justifyContent: 'center',
},
});
Expo.registerRootComponent(App);
Solution suggested by #Matt but as soon as I put the navigation={this.props.navigation} it complains. undefined is not an object ( evaluating this.props.navigation )
renderRow(property){
return (
<ListItem
property={property}
navigation={this.props.navigation}
onPress={() => {
console.log( '70-on Press inside renderRow ');
}}/>
);
}
If the component is not a screen you have to import the navigation.
Try this:
import React from 'react';
import { Button } 'react-native';
import { withNavigation } from 'react-navigation';
class MyBackButton extends React.Component {
render() {
return <Button title="Back" onPress={() => { this.props.navigation.goBack() }} />;
}
}
// withNavigation returns a component that wraps MyBackButton and passes in the
// navigation prop
export default withNavigation(MyBackButton);
For more info check out
https://reactnavigation.org/docs/connecting-navigation-prop.html
This answer was written for old version of react-navigation V1
I had the same exact problem, and I found out that this.props.navigation is injected only in components that are registered as screen in StackNavigator or TabbNavigator.
but in general you can use navigate from NavigationActions class (source here https://v1.reactnavigation.org/docs/navigation-actions.html#navigate)
note: NavigationActions.navigate receives parameters in different way but works the same way.
so this working for me
import { NavigationActions } from 'react-navigation';
let {navigate} = NavigationActions;
renderRow(property) {
return (
<ListItem
property={property}
onPress={() => { navigate({
routeName: 'OtherRoute'
});
}}/>
);
}
<MyComponent navigation={this.props.navigation}/>
Main problem is here. You didn't define your prop navigation in component. You should add this.
Here's how you can use navigation.navigate inside a functional component:
import { Text, TouchableHighlight } from 'react-native';
const MyComponent = ({ navigation }) => (
<TouchableHighlight
onPress={() => navigation.navigate('OtherRoute')}
underlayColor="blue"/>
<Text>Click to Navigate!</Text>
</TouchableHighlight>
);
export default MyComponent;
When you render MyComponent, you will need to pass navigation as a prop. For example, assume HomeContainer is a screen component:
import React from 'react';
import MyComponent from './MyComponent';
export default HomeContainer extends React.Component {
render() {
return (
<MyComponent navigation={this.props.navigation}/>
);
}
}
Change your renderRow method to the following:
renderRow(property) {
return (
<ListItem
property={property}
onPress={() => { this.props.navigation.navigate('OtherRoute'); }}/>
);
}
where 'OtherRoute' is the name of the route you want to navigate to for that row.
I try to implement a navigation in React Native with react-navigation according to this tutorial, but I am facing the following error when launching the app:
undefined is not an object (Evaluating 'this.props.navigation.navigate')
render index.android.js:17:12
My index.android.js:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Button
} from 'react-native';
import {
StackNavigator,
} from 'react-navigation';
export default class Abc extends Component {
render() {
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Button
title="Go to List"
onPress={() =>
navigate('Liste')
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
export class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<Button
title="Go to List"
onPress={() =>
navigate('Profile')
}
/>
);
}
}
export class Liste extends React.Component {
static navigationOptions = {
title: 'Liste',
};
render() {
const { navigate } = this.props.navigation;
return (
<Button
title="Liste"
/>
);
}
}
const App = StackNavigator({
Home: { screen: HomeScreen },
Liste: { screen: Liste },
});
AppRegistry.registerComponent('Abc', () => Abc);
Do you have any suggestions? Thank you!
I believe what you wanted to do is to register your App component in your AppRegistry instead of the Abc component.
The reason that you ran into this undefined is not an object (Evaluating 'this.props.navigation.navigate') is because the props.navigation is not available in your Abc component. If you look closely to the bottom of your code,
you have:
const App = StackNavigator({
Home: { screen: HomeScreen },
Liste: { screen: Liste },
});
AppRegistry.registerComponent('Abc', () => Abc);
So you have created a StackNavigator component, but you are not registering this component but instead you are registering the Abc component in the AppRegistry, and since Abc component is not any of the screen under your StackNavigator, it won't have access to the this.props.navigation thus you would run into the exception.
You don't have Profile screen.
If you want to go to Liste screen from HomeScreen use:
onPress={() => navigate('Liste')}
If you want to go to Profile screen then you need to make Profile Screen and add it on StackNavigator, example:
export class Profile extends React.Component {
static navigationOptions = {
title: 'Profile',
};
render() {
const { navigate } = this.props.navigation;
return (
<Button
title="Profile"
/>
);
}
}
const App = StackNavigator({
Home: { screen: HomeScreen },
Liste: { screen: Liste },
Profile: {screen: Profile},
});
almost 3 years since the original question was asked. I had the same problem and landed on this page.
react-navigation is v5 now. The problem I had was because 'navigation' was not passing to the child component.
After a bit more research I fixed the problem using this solution from here.
I need to pass the props to child component like this.
<GoToButton navigation={props.navigation} />
https://reactnavigation.org/docs/connecting-navigation-prop
Hopefully this helps to new comers like me.