How do I create a StackNavigator for two screens? - android

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')

Related

How to handle back button behavior in bottom tabs in React Native?

I have a #react-navigation/bottom-tabs navigator when my app opens whose contents are like:
<Tab.Navigator
tabBarOptions={{
activeTintColor: '#77dd77',
inactiveTintColor: 'gray',
}}
tabBar={props => <MyTabBar {...props} />}
backBehavior={"history"}
>
<Tab.Screen
name="Home"
component={Home}
options={{ title: 'Home' }}
/>
<Tab.Screen
name="Orders"
component={Orders}
options={{ title: 'Orders' }}
/>
<Tab.Screen
name="Profile"
component={Profile}
options={{ title: 'Profile' }}
/>
</Tab.Navigator>
I have a BackHandler in my code that makes the app exit when back button is pressed from the home page. Everything is fine and I have checked that the backhandler gets called when back button is pressed.
But when I switch to any other tab and then return to the homepage and press back to exit the app, backhandler stops working and the app shows error "The action 'GO_BACK'was not handled by any navigator. Is there any screen to go back to?"
This is a development-only warning but in the signed version, the app doesn't show any error and doesn't even exit.
How can I address this 'GO_BACK' action?
I was facing a similar issue, just found out the solution.
The problem was that I was trying to handle backHandler from the screen itself, but it just doesn't work like that with tab navigator (and maybe with react-navigation as a whole? I dunno).
Any way, you just need to add a listener for 'focus' (~componentDidMount) and 'blur' (~componentWillUnmount) like this:
<Tab.Screen name="Home" component={HomeScreen}
listeners={{ focus: () => BackHandler.addEventListener('hardwareBackPress',handleBackButton)
,blur: () => BackHandler.removeEventListener('hardwareBackPress',handleBackButton)
}}
/>
Where:
function handleBackButton () {
BackHandler.exitApp();
return true;
}
Now the backHandler function works in this way only in HomeScreen, and as usual in other screens.
React Navigation (version 5) has already handled the native back button on bottom tabs.
I was stuck with this issue for one day and tried so many ways.
I ended up finding out in react-navigation version_5 that they have included it with just one line.
https://reactnavigation.org/docs/bottom-tab-navigator/#backbehavior
Add the prop backBehaviour = "initialRoute" to your <Tab.Navigator/> and that will handle all native back button on tabs
<Tab.Navigator backBehaviour = "initialRoute" >
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
I was using backhandler in all my tabs to create a navigation flow I wanted. Turns out this was creating the issue. After removing the backhandlers from the rest tabs, the app runs smoothly now.
I am now trying to experiment more with backBehavior in tab navigator to try to get the flow I want.
Writing my problem here gave me a clearer view of my problem!
My answer is not related to your question. But I hope it will help you. I created a custom hook to handle back press in all bottom tabs.
// back handler hook
import React from 'react';
import {useEffect} from 'react';
import {BackHandler} from 'react-native';
export const useBackButton = (props, handler) => {
useEffect(() => {
props.navigation.addListener('focus', () => {
BackHandler.addEventListener('hardwareBackPress', handler);
});
props.navigation.addListener('blur', () => {
BackHandler.removeEventListener('hardwareBackPress', handler);
});
}, [handler]);
};
Now In home screen I added this
const onBackPress = () => {
BackHandler.exitApp();
return true;
};
useBackButton(props, onBackPress);
And In other screens I added following.
const onBackPress = () => {
props.navigation.goBack();
return true;
};
useBackButton(props, onBackPress);

React-navigation touches sometimes don't work

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

Error using StackNavigator with switching screens

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.

Expo / React Native - Add clickable icon along with StackNavigator objects

Is there a way to add a clickable icon along with StackNavigator objects?
This is a working code and it will show a "createBottomTabNavigator" with only one icon that will lead to "OtherScreen" on press.
const OtherStack = createStackNavigator({
Other: OtherScreen,
});
OtherStack.navigationOptions = {
tabBarLabel: 'Other',
tabBarIcon: ({
focused
}) => (
<TabBarIcon focused={focused} name='archive' />
),
};
export default createBottomTabNavigator({
OtherStack
});
I would like to add a share icon to the same "createBottomTabNavigator" so it will be aligned with all other icons but I don't want it to lead to a different screen. I just want it to open a share dialog.
I can't find a solution for this. Is it possible at all? Can someone help please?
You can add a button to the tab bar that doesn't lead to another screen by overriding tabBarOnPress for the route. Here's an example: https://snack.expo.io/#notbrent/playful-almond
The important part is here:
Placeholder: {
screen: () => null,
navigationOptions: {
tabBarOnPress: ({ navigation, defaultHandler }) => {
// don't call default handler!
alert('pressed placeholder!');
},
},
},

Android standalone menu button with React native

I need to make a standalone (without additional bars or else) menu button in my React Native (v 0.47.2) Android app:
On touch it has to open side menu:
Which component I need to use?
You are using react-navigation. Use the StackNavigator. The StackNavigator can set Headers. In the Header, there is a prop to which you can pass an Icon (or any Component).
Here an example:
// all your other imports
import Icon from "react-native-vector-icons/Ionicons";
import {
Platform,
} from "react-native";
const MenuButton = ({ navigate }) => {
return (
<Icon
name={Platform.OS === "ios" ? "ios-menu-outline" : "md-menu"}
onPress={() => navigate("DrawerOpen")}
/>
)
}
StackNavigator({
Notifications: {
screen: Example,
navigationOptions: ({ navigation }) => ({
headerLeft: <MenuButton {...navigation} />,
}),
},
The headerLeft (or headerRight) can be used for you case (Documentation). Here I pass <MenuButton /> component. You can set the color of the StackNavigators Header to the backgroundColor of your app, or transparent. That way, there won`t be anything visible, but the menu button.
Of yourse you would need to stack you StackNavigator in a DrawerNavigator for the onPress={() => navigate("DrawerOpen")} to work.
In the DrawerNavigator you can use a contentComponent which you pass your custom component, that contains your menu.
Here is a more complex setup http://rationalappdev.com/cross-platform-navigation-in-react-native/

Categories

Resources