I'm trying to make a SignUp/Login screen with a stackNavigator.
But when I try to navigate from the login page to the signup page.
I've tried creating a const that would be the navigate property but I get the same. If I do all my screens in the same file with the stackNavigator I don't get error but this is not really what I want to do.
Here is my Login Screen first (before getting to this screen in index.js I call the Welcome class to check if the user is logged in are not and I return the if he's not
export default class Login extends React.Component{
render(){
return(
<ScrollView style={{padding : 20}}>
<Text style={styles.title}>
Login
</Text>
<TextInput style={styles.input} placeholder="Email Address"/>
<TextInput style={styles.input} placeholder="Password" />
<View style={{margin : 7}}/>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('Signup')}
>
<Text> Don't have an account? Click here</Text>
</TouchableOpacity>
<Button
onPress={this.props.onLoginPress}
title="Log in"
color='grey'
/>
</ScrollView>
);
}
}
My sign up screen is pretty empty it only returns a View with a title as of right now
And finally here's my StackNavigator file
const AppNavigator = createStackNavigator({
Welcome: {
screen : Welcome
} ,
Login: {
screen: Login
},
Signup : {
screen : Signup
},
},
{
initialRouteName: "Welcome"
});
export default createAppContainer(AppNavigator);
Where Welcome is the file that checked if user is logged in are not
The error I get is "error undefined is not an object (evaluating '_this.props.navigation.navigate')." When I click the in Login.js
checked your repo. Your welcome screen first checks if the user is logged in or not and if not logged in, it renders the login screen. You're not navigating from Welcome screen to Login screen, but rendering another Login screen. The problem lies here. I'll give an example:
In your phone, say iPhone X, you downloaded and installed Facebook from the App Store. Then you logged into your account. That means, the Facebook app in your phone is now configured to use your account. Now, if you want to post your photo, you wont be able to do it if you download and install another Facebook app and try to post from there. You will have to use the same old app which is configured to use your account. (Actually you won't be able to download two at a time :P)
In the same way, you should tell the stack navigator to navigate to the stack navigator's login screen, not render another login screen. The new login screen you created won't have the navigation prop, unless you pass one.
But there is another way. You can render the signup screen the same way you render your login screen. Copy this code to your Welcome.js:
import React from 'react';
import { Text, View, Button } from 'react-native';
import LoginScreen from './LoginScreen';
import SignupScreen from './Signup';
import DrawerNavigator from '../navigation/DrawerNavigator';
const notLoggedIn=0, loggedIn=1, signingUp=2;
export default class Welcome extends React.Component{
constructor(props){
super(props);
this.state={
state: notLoggedIn
}
}
render(){
switch (this.state.state){
case notLoggedIn:
return (
<LoginScreen
onLoginPress={()=>this.setState({state:loggedIn})}
onSignUpPress={()=>this.setState({state:signingUp})}
/>
);
case loggedIn:
return <DrawerNavigator />;
case signingUp:
return <SignupScreen />; //Add onBackPress={()=>this.setState({state:notLoggedIn})}} prop here if you want
}
}
}
Then in your Login.js, change onPress prop from this.props.navigation.navigate('Signup') to this.props.onSignUpPress().
You won't need the stack navigator now.(You've your own navigator now) So, instead of using that stack navigator, you can now directly use your Welcome screen. Now you can just delete the stacknavigator.js file.
This should work out, I guess.
Tip: Saw that you have many unused styles and imports in your files. If you're not really going to use them, you can just remove those lines. That's up to you.
EDIT:
To pass a function into a navigator, you can use the screenProps prop.
Edit your Welcome.js:
case loggedIn:
return <DrawerNavigator logout={()=>this.setState({state:notLoggedIn})} />;
DrawerNavigator.js:
...
const DrawerNavigator = createAppContainer(createDrawerNavigator(
...
));
export default class navigator extends React.Component {
render(){
return <DrawerNavigator screenProps={{logout: this.props.logout}} />
}
}
And inside your logout.js, call the function this.props.screenProps.logout() from wherever you want.
This should work.
Related
Some background for context...I'm creating a react-native app that will live in it's own repository. This app will be integrated into other web, iOS, and Android apps. I'm currently working with the Android app. This is an existing native app written in Java.
I've "wrapped" it with a super basic React Native app, following the React Native Docs. For testing purposes, to mimic what will end up being the production app, I have a native activity, MainActivity in the Android app, with a button that opens the second activity, ReactNativeActivity which is loads my React Native app. This is working - the React Native app loads. The React Native App has a button that should navigate to a second screen within the React Native app (using react-navigation). When I press that button, instead of navigating to the next screen in my stack, it instead reloads MainActivity from the native Android app. Running the same thing on web there is no issue with navigation, so I know the screens are hooked up right. I'm not new to React Native, but I am new to integrating with existing Native applications.
Edited to add some code as requested
The Navigator and the button are as follows:
// navigator.tsx
import React from "react";
import { NavigationContainer } from "#react-navigation/native";
import { createNativeStackNavigator } from "#react-navigation/native-stack";
import { HomeScreen } from "../screens/Home";
import Demo from "../screens/Demo";
export type NavigationStackParamList = {
HomeScreen: undefined;
Demo: undefined;
};
const Stack = createNativeStackNavigator<NavigationStackParamList>();
export const Navigation = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen component={Demo} name="Demo" />
<Stack.Screen
component={HomeScreen}
name="HomeScreen"
options={{ title: "Home" }}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
// Demo Screen
const Demo: React.FC<DemoScreenProps> = ({ navigation }) => {
return (
<View style={styles.app}>
<View style={styles.info}>
<Text style={styles.title}>Demo</Text>
<Text>Running on: </Text>
<Text style={styles.platform}>{Platform.OS}</Text>
</View>
<Map />
<Button
onPress={() => navigation.navigate("HomeScreen")}
title="Go to Test Screen"
/>
</View>
);
};
Pressing "Go to Test Screen" above, is what is causing the MainActivity to reload, rather than the correct screen.
I am experiencing a very weird behaviour in my android emulator when using react-navigation v5 in my react native application, which makes me think is a bug. I found out that there is an open issue on the official react-native-navigation page. See here.
In every secondary screen (see UserProfileScreen below), in many react native-elements like TouchableOpacity Button or TextInput the onPress event doesn't work. It only works in the main screen of the navigator. (See HomeScreen below)
Here is an example of how I create my stack navigator:
import {createStackNavigator} from '#react-navigation/stack';
// some imports here
const HomeStackNavigator = createStackNavigator();
export const HomeNavigator = () => {
return (
<HomeStackNavigator.Navigator>
<HomeStackNavigator.Screen name="HomeScreen" component={HomeScreen}/>
<HomeStackNavigator.Screen name="UserProfileScreen" component={UserProfileScreen}/>
<HomeStackNavigator.Screen name="UserSettingsScreen" component={UserSettingsScreen}/>
</HomeStackNavigator.Navigator>
)
};
As a PoC I have the same code in the main and secondary screen:
<TouchableOpacity
onPress={() => Alert.alert("You pressed me!")} >
<Text> touch me</Text>
</TouchableOpacity>
and I see the alert only in the main screen (HomeScreen).
If I make UserProfileScreen as the fist screen in my stack navigator above then it works (the onPress event) fine on this screen but not in the HomeScreen. So it seems that the onPress event is triggered only in the main screen!. On IOS it works fine in all screens. Any idea ? Let me know if you need some more code snippets.
I'm hoping this is a known problem as I can't provide much to go on to get help solving it.
I'm using react-navigation for my app with the following setup:
AppContainer
Splash Screen
Setup Screen (A stack navigator)
Main screen (A stack navigator)
When my app starts it goes to the splash screen which decides if this is the first time running or not and depending on this decision calls this.props.navigation.navigate() with either main screen or setup screen.
So far this all works and is fairly standard.
When my app is launched for the first time the user is sent to the setup screen where they navigate through a series of screens entering data and selecting a next button to proceed to the next screen. This is where the problem occur. My first screen simply has some text and a next button (which is a regular button component) which when clicked calls this.props.navigation.push('nextviewname', {data: data}) to move to the next view.
The next view contains a textinput as well as back and next buttons which is where I'm having problems. When I reach this screen after freshly installing a release version of my app onto my Android X phone none of the inputs work. I can't:
Click next of back
Click the back arrow in the top left that is part of the header
Click the text input (the cursor does briefly show up in the text input but the keyboard never appears)
Click the hardware back key
On very rare occasions some of the above does work (e.g. the text input will sometimes work) and sometimes I'll even make it to the next step of my setup but it's rare that I make it all the way through
Weirdly this all works when I'm debugging my app on my phone.
Update: I've just tested on an Android 9 emulator and I'm getting the same issue.
Update 2: This is getting weird, when the app is in a broken state I can still bring up the react native debug menu however when I click Toggle Inspector nothing happens (i.e. I don't get the inspector UI). It's looking like this is somehow breaking everything.
Has anyone seen/solved this issue before? At the moment it's effectively made my app useless.
Update 3: Some code to hopefully make things clearer:
const SetupUser = createStackNavigator(
{
SetupUser: WelcomeScreen,
SetupName: UserName,
SetupCurrentWeight: CurrentWeight,
SetupGoalWeight: GoalWeight,
SetupGoalDate: GoalDate,
Summary: Summary,
LogWeight: LogWeight,
},
{
defaultNavigationOptions: {
headerStyle: {
backgroundColor: '#001830',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
},
);
const MainApp = createStackNavigator(
{
LogWeight: LogWeight,
LogWeightSummary: LogWeightSummary,
},
{
defaultNavigationOptions: {
headerStyle: {
backgroundColor: '#001830',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
},
);
export default createAppContainer(
createSwitchNavigator(
{
MainApp: MainApp,
SplashScreen: SplashScreen,
SetupUser: SetupUser,
},
{
initialRouteName: 'SplashScreen',
},
),
);
In getting this code snippit together (I've removed the tab navigator as the error is still there even without it) I think I've managed to track down the source of the issue however I'm still not sure how to fix it. The first view loaded is the splash screen which looks like this:
export default class SplashScreen extends React.Component {
constructor(props) {
super(props);
GoBinder.verifyDatabase()
.then(GoBinder.getLastUser())
.then(user => {
this.props.navigation.navigate(
user.user == null ? 'SetupUser' : 'MainApp',
);
})
.catch(error => {
GoBinder.toast('Error while checking initial user state: ' + error);
});
}
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
In the above code, GoBinder.verifyDatabase() and GoBinder.getLastUser() are calls to native code which perform some database operation to get things setup and check to see if there are any existing users. I believe the issue is this.props.navigation.navigate is firing too quickly which is causing react-navigation to load the next screen but get messed up in the process.
I've tried following the suggestions in this post https://www.novemberfive.co/blog/react-performance-navigation-animations about moving blocking code into InteractionManager.runAfterInteractions under componentDidMount however this made no difference. However, it I manually trigger the move to the new screen using a button everything works correctly so its really looking like the act of programatically changing screens is messing things up.
Update 4:
Looks like I jumped the gun a bit, its still freezing up a fair bit, moving my answer into an update:
So I'm not sure if this is the best solution but I have found a workaround. I am now calling my database code in InteractionManager.runArfetInteraction() as suggested in https://www.novemberfive.co/blog/react-performance-navigation-animations. I am then calling setState with the result of the DB functions to store the result. Finally, I am using a callback on setState to trigger the actual navigation. My complete working code is:
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
GoBinder.verifyDatabase()
.then(
GoBinder.getLastUser().then(user => {
this.setState(
{
user: user.user,
},
() => {
this.props.navigation.navigate(
this.state.user == null ? 'SetupUser' : 'MainApp',
);
},
);
}),
)
.catch(error => {
GoBinder.toast('Error while checking initial user state: ' + error);
});
});
}
Thanks for your help
I'm using react-navigation to build my first react-native app. I have a home screen on which I want to have a sign up button.
In my router file, I have the following:
export const RootNavigator = StackNavigator({
Home: {
screen: Home
},
SignUp: {
screen: SignUp
}
}, {
mode: 'modal',
headerMode: 'none',
}
);
Here is my Home.js file:
export default class Home extends Component {
render() {
const buttons = ['SIGN IN', 'SIGN UP']
const { selectedIndex } = this.state
return (
<View style={styles.logoContainer} >
<Button
raised
backgroundColor="#333"
title='SIGN IN' />
<Button
raised
onPress = {this.props.navigation.navigate('SignUp')}
backgroundColor="#333"
title='SIGN UP' />
</View>
);
}
}
In my index file, I render this RootNavigator component.
When I load up the simulator, the navigation goes to the Home screen but immediately animates to the SignUp screen.
I'm quite new to this but what I want to be able to do is have the home screen appear, and be able to navigate to the sign up screen when I click on the Sign Up button (which is why I have the Sign up screen in the StackNavigator).
Any thoughts on how to accomplish this?
You are executing function rather than passing as a property. Parenthesizes execute functions.
This,
onPress={this.props.navigation.navigate('SignUp')}
should be like below
onPress={() => this.props.navigation.navigate('SignUp')}
Your problem is not that you need more navigators. You have defined your onPress as the result of this.props.navigation.navigate('SignUp') however what you need is a function handle.
Put the following there and it will work as you intended;
() => this.props.navigation.navigate('SignUp')
I am trying to navigate my first page to the second page by tapping a button.
Once we are at the second page, we will never go back by pressing back button.
Just tap, switch, and never go back.
Is there any simple way to do this?
I mean, I just don't want to use the component Navigation or Navigator. They are too complex.
I'm using React Navigation from guide of Facebook.
You can read more about this library from:
https://facebook.github.io/react-native/docs/navigation.html
Or react navigation's website:
https://reactnavigation.org/docs/intro/
Use this library to avoid the navigator. This is awesome
https://github.com/aksonov/react-native-router-flux
Once your scene keys are defined, you can navigate to them simply by calling Actions.keyname() on any screen
I don't know how complex your app is, but if it's really simple you can do it this way:
const setScreen = (screen) => {
setState({screen: screen})
}
render() {
<View>
{
switch(this.state.screen) {
case 'firstScreen': (
<View>
<Text>First Screen</Text>
<TouchableHighlight onPress={this.setScreen('secondScreen')}>
<Text>Go to second screen</Text>
</TouchableHighlight>
</View>
)
case 'secondScreen': (
<View>
<Text>Second Screen</Text>
<TouchableHighlight onPress={this.setScreen('firstScreen')}>
<Text>Go to first screen</Text>
</TouchableHighlight>
</View>
)
}
}
</View>
}
This mainly happens when we try to install a new package in our project and it does not get installed properly. I resolved this by
Delete android/app/build/intermediates/signing_config/debug/out/signing-config.json file.
Run yarn in the project root path.