React Navigation + Redux close App on Back Pressed - android

After converting the app to redux, my react-navigation got some problem. Previously, before integrating with redux, when I press back button (Physical button) react-navigation back to the previous screen. After integrating with redux, the back button will close the app. But, it's still working with goBack() function.
I'm following the guide: https://reactnavigation.org/docs/guides/redux
And read some code from here : https://github.com/react-community/react-navigation/tree/master/examples/ReduxExample
And, this is my Navigator configuration
export const AppNavigator = StackNavigator(
{
Home: { screen: HomeScreen },
ChatDetail: { screen: ChatDetail },
PulsaDetail: { screen: PulsaDetailScreen },
Pulsa: { screen: Pulsa }
},
{
headerMode: 'none',
}
)
class AppWithNavigation extends Component {
render(){
return(
<AppNavigator navigation={ addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})} />
)
}
}
const mapStateToProps = (state) => ({
nav: state.nav
})
export default connect(mapStateToProps)(AppWithNavigation)
EDIT: It's can be done with manual handle & dispatch back action, but it's can't do it automaticlly? just like before using redux?
BackHandler.addEventListener('hardwareBackPress',() => {
this.props.goBack()
return true
})

After post Github issue in react-navigation repository, I got the answer.
Should add manually the back listener on top of screen / component
// App.js
import { BackAndroid } from 'react-native'
// [...]
componentDidMount() {
BackAndroid.addEventListener('backPress', () => {
const { dispatch, nav } = this.props
if (shouldCloseApp(nav)) return false
dispatch({ type: 'Back' })
return true
})
}
componentWillUnmount() {
BackAndroid.removeEventListener('backPress')
}
// [...]
https://github.com/react-community/react-navigation/issues/2117
https://github.com/react-community/react-navigation/issues/117
UPDATE:
https://facebook.github.io/react-native/docs/backhandler

Related

How to handle hardware back button android in React Native?

I get a problem to handle hardware back button android in React Native, I want to back in specific page/component when I press back button in hardware/device, but I always get error 'undefined is not an object (Evaluating 'this.props.navigation').
this is my script :
import { Platform, StyleSheet, Text, View, BackHandler } from 'react-native';
import { createStackNavigator, createAppContainer, NavigationActions, createBottomTabNavigator } from 'react-navigation';
import OnBoarding from './apps/src/onBoarding/OnBoarding';
import Welcome from './apps/src/welcome/Welcome';
import Login from './apps/src/login/Login';
const MainNavigator = createStackNavigator({
OnBoarding: OnBoarding,
Welcome: Welcome,
Login: Login
},{
initialRouteName: 'OnBoarding',
headerMode: 'none',
navigationOptions: {
headerVisible: false
}
});
const Approot = createAppContainer(MainNavigator);
var screen = '';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
routeName: ''
}
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton() {
if(screen == 'Login') {
this.props.navigation.navigate('OnBoarding');
}else{
return false;
}
}
getActiveRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return this.getActiveRouteName(route);
}
return route.routeName;
}
render() {
return <Approot onNavigationStateChange={(prevState, currentState) => {
screen = this.getActiveRouteName(currentState)
}} />;
}
}
in this case, I have 3 component, they are OnBoarding, Welcome, Login, when postion in Login I want to back to OnBoarding when press hardware back button, please help me to solve this problem.
Thanks.
You will have to use the navigation services as you want to navigate from outside of navigation (root of the app). you can follow documentation.
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html

React Navigation 3: Back button in Android doesn't back to previous screen

I am upgrading my router configuration of my React Native app using React Navigation 3 and many things has been improved now but I don't understand why when I press the back button in Android, is not sending me to the previous view and instead is sending me to the Home one.
My routes
const drawerConfig = {
initialRouteName: 'Home',
contentComponent: SideMenu,
drawerWidth: width,
}
const MainDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: Home,
},
Company: {
screen: Company,
},
Gifts: {
screen: Gifts,
},
Jobs: {
screen: Jobs,
},
Job: {
screen: Job,
},
Contact: {
screen: Contact
}
},
drawerConfig,
);
const InitialStack = createStackNavigator(
{
Menu: {
screen: Menu,
path: 'menu/',
}
},
{
initialRouteName: 'Menu',
headerMode: 'none',
}
);
const SwitchNavigator = createSwitchNavigator(
{
Init: InitialStack,
App: MainDrawerNavigator,
},
{
initialRouteName: 'Init',
}
);
const AppContainer = createAppContainer(SwitchNavigator);
export default AppContainer;
If I do this: Open Drawer, open Jobs then press in a job button to load the Job view the flow is working well but If I press the back button in the Job view is not showing the Jobs but the Home.
I am navigation using this.props.navigation.navigate('...') because the push is not working.
Do you know why?
I am using react-navigation 3.5.1 and react-native 0.59.3
I just figured out.
I was not writing the right configuration for my routes because if I wanted to back from Job to Jobs instead to Home, a stack was necessary for every "stack" of views I needed, so now they can work exactly how I want, the pop is working great without using the back handler event.
Like this:
// Jobs stack
const JobsStack = createStackNavigator(
{
JobList: {
screen: Jobs,
},
Job: {
screen: Job,
},
},
{
headerMode: 'none',
}
);
// Main drawer
const MainDrawerNavigator = createDrawerNavigator(
{
...
Jobs: JobsStack,
Contact: {
screen: Contact
}
...
},
drawerConfig,
);
Thank you to all the people that helped me :)
You can import BackHandler from 'react-native' and can use the code like below in your screen class from where you want to go back:
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = () => {
this.props.navigation.goBack();
return true;
};

Handling BackHandler

Being a newbie in RN programming, I'm trying to handle android hardware button. But pressing it on screen leads to simultaneously going to previous screen and closing app.
My StackNavigator looks like:
const navigatorApp = StackNavigator({
Screen1: { screen: Screen1 },
Screen2: { screen: Screen2 },
Screen3: { screen: Screen3 },
Screen4: { screen: Screen4 }
})
I tried to make a global backpress handling for screens like
class HandleHavigation extends React.Component {
componentWillMount () {
if (Platform.OS === 'android') return
BackHandler.addEventListener('hardwareBackPress', () => {
const { dispatch, nav } = this.props
if (nav.routes.length === 1 && (nav.routes[0].routeName === 'Screen1')) {
return false
}
dispatch({ type: 'Navigation/BACK' })
return true
})
}
componentWillUnmount () {
if (Platform.OS === 'android') return
BackHandler.removeEventListener('hardwareBackPress')
}
render () {
return <navigatorApp navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
addListener: createReduxBoundAddListener('root')
})} />
}
}
const mapStateToProps = state => ({ nav: state.reducer })
export default connect(mapStateToProps)(HandleNavigation)
I also tried some given in other questions solutions, but nothing helped to prevent app closing.
I also thought about realizing backHandler on every screen.
In my app every screen contains function onPress for top button. That is why I tried to copy this action to hardware button using Backhandler. But all I get - screen goes back, and the app hides at the same time.
Is there any solution in my case to prevent closing app by pressing hw backbutton?
You can use BackHandler to exit/close the application:
import { BackHandler } from 'react-native';
BackHandler.exitApp();
Use react-navigation it has inbuilt backhandler.

Prevent user from returning to previous screen StackNavigator

I want to do a simple task: after a successful login redirect the user to his home page. I use react-navigation's StackNavigator for that purpose:
// App.js
class App extends Component {
render() {
return (<RootStack />);
}
}
const RootStack = createStackNavigator(
{
Login: { screen: Login, navigationOptions: { header: null }},
Home: { screen: Home, navigationOptions: { header: null }}
},
{
initialRouteName: 'Root'
}
)
How do I prevent user from returning to Login screen after login? To prevent use back button in android, I would use this one:
// Home.js
import React, { Component} from 'react';
import { ... , BackHandler } from 'react-native';
class Home extends Component {
constructor(props) {
super(props);
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid);
}
onBackButtonPressAndroid = () => {
return true;
}
}
But doing this way, I disable back button at all. Is there another way to achieve the goal?
The react native documentation has an excellent page on how to make an authentication flow.
You may not be familiar with SwitchNavigator yet. The purpose of SwitchNavigator is to only ever show one screen at a time. By default, it does not handle back actions and it resets routes to their default state when you switch away. This is the exact behavior that we want from the authentication flow: when users sign in, we want to throw away the state of the authentication flow and unmount all of the screens, and when we press the hardware back button we expect to not be able to go back to the authentication flow. We switch between routes in the SwitchNavigator by using the navigate action. You can read more about the SwitchNavigator in the API reference.
According to the documentation, this SwitchNavigator is implemented like this:
import { createSwitchNavigator, createStackNavigator } from 'react-navigation';
// Implementation of HomeScreen, OtherScreen, SignInScreen, AuthLoadingScreen
// goes here.
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
);
So, to achieve what you are looking for, you would want to change your RootStack to the following (note i have not tested this code):
const RootStack = createSwitchNavigator(
{
Loading: ,//put your loading screen here
Auth: { screen: Login, navigationOptions: { header: null }},
App: { screen: Home, navigationOptions: { header: null }}
},
{
initialRouteName: 'Loading'
}
)
Then, in your loading screen, you would fetch whatever state is needed to determine if the user is already signed in and you would call either this.props.navigation.navigate('App'); to skip the login screen and take the user directly to your app, or this.props.navigation.navigate('Auth'); to send your user to the login page. the SwitchNavigator automatically handles disabling back navigation for you.

How to finish current component while navigating to next component in react native

Hi I am trying to navigate to next component using navigate function. I am using react-navigation for the navigation among multiple components.
Suppose I have index.android.js and DashboardScreen.js component. I am trying to navigate to DashboardScreen.js component from index component.
It is navigating but index component always retain in component stack. when I press back then it opens index.android.js which should not be. Does anyone know how to manage this in react-native. In Android, finish() works for this.
navigate("DashboardScreen");
When I am navigating from SplashScreen to EnableNotification then SplashScreen should be destroyed, if I am navigating from EnableNotification to CreateMessage then EnableNotification should be destroyed and if I am navigating from CreateMessage to DashboardScreen then CreateMessage should be destroyed. As of now no component is being destroyed.
index.android.js
class SplashScreen extends Component {
render() {
if (__DEV__) {
console.disableYellowBox = true;
}
const { navigate } = this.props.navigation;
AsyncStorage.getItem("#ProductTour:key").then(value => {
console.log(value);
if (value) {
navigate("DashboardScreen");
}
});
return (
....
);
}
}
const App = StackNavigator(
{
Splash: {
screen: SplashScreen,
navigationOptions: {
header: {
visible: false
}
}
},
EnableNotification: {
screen: EnableNotificationScreen,
navigationOptions: {
header: {
visible: false
}
}
},
CreateMessage: {
screen: CreateMessageScreen,
navigationOptions: {
header: {
visible: false
}
}
},
DashboardScreen: {
screen: DashboardScreen,
navigationOptions: {
header: {
visible: false
}
}
}
},
{
initialRouteName: "Splash"
}
);
Just use 'replace' in place of 'navigate'
this.props.navigation.replace('Your Next Component Name')
First of all, using AsyncStorage in an a synchronous function (most especially a lifecycle one) is such a bad idea. You should typically keep ASyncStorage to places in your folder / app structure that make sense for where you access/keep data but since that's not the question I will just mention it quickly here...
Basically you are asking to navigate once the ASync method completes itself based on EVERY render... Those new to RN should know that an awful lot of things can cause a render to fire. Some cases, the render function can fire (I have seen this many times before) 10 or more times before finalizing the last render. This means you would have fired that ASyncStorage method 10 times... definitely something to think about when implementing this stuff. So more or less, the .then(); part of the AsyncStorage function is firing long after the render has already finished doing it's thing. If it was a reasonable approach to use I would say to put the return part of the render function inside of the .then((value) => { return ( ... ); });. But this is an even worse idea. Basically you need the right lifecycle method here and it's NOT the render method.
Anyway, since I have never used this component library before I can only help nudge you in the right direction so here goes... These docs on their webpage seem to say that you need a reference to the props navigator passed down to the component in which you are using it. So if you created the navigator in this class, you would use this.refs.whateverYouNamedTheNavigatorReference.navigate('SomeItemName'). If you are in the class that has been passed this navigator as a prop, you use this.props.passNavigatorPropName.navigate('SomeItemName'). I see you are using variable deconstruction to get the navigate callback but I would caution on doing this, this way because I have seen it cause errors by grabbing an old version of the navigate function or its parent reference by accident and causing a cascading error effect.
Also, if you are going to be using ASyncStorage in a component file (again, would recommend putting this in a component/class where your data is accessed throughout the app...) and you are going to use it to decide the app should navigate forwards/backwards... definitely remove it from the render function and put it in maybe the constructor, componentWillReceiveProps, componentDidReceiveProps or componentWillUpdate lifecycle functions. That way it fires based on an update, a new passed prop obj or one time as the component is built. Anything is better than firing it every single render.
Lastly, I do not know what you have setup for your StackNavigator route stack object but you would need to have the keyword you used "DashboardScreen" in there pointing to an actual component that has been imported properly. The "DashboardScreen" keyword most likely would connect in your StackNavigator object to some component import like so...
import Dashboard from '../Views/DashboardScreenView';
StackNavigator({
DashboardScreen: {
screen: Dashboard,
path: 'dashboard/:main',
navigationOptions: null,
},
});
There is a simple way here: use "replace" (reference link repleace in navigation ,For example, you are at the screen "Login" ,
and you want to move to screen "Home", insert this code in screen "Login"
<TouchableOpacity onPress={() => { this.login() }}>
<Text}>Click me to Login</Text>
</TouchableOpacity>
and method login:
login(){
this.props.navigation.replace('Home')
}
Screen "Login" will be replaced by "Home", in Android, press Back Button =>app exit, no back screen "Login"
Based on your requirement, i suggest following setup:
SplashNavigator.js
const SplashNavigator = StackNavigator({
Splash: {
screen: SplashScreen,
navigationOptions: {
header: {
visible: false
}
}
}
});
AppNavigator.js
const AppNavigator = StackNavigator(
{
EnableNotification: {
screen: EnableNotificationScreen,
navigationOptions: {
header: {
visible: false
}
}
},
CreateMessage: {
screen: CreateMessageScreen,
navigationOptions: {
header: {
visible: false
}
}
},
Dashboard: {
screen: DashboardScreen,
navigationOptions: {
header: {
visible: false
}
}
}
},
{
initialRouteName: "EnableNotification"
}
);
In your index.android.js, you will render the SplashNavigator.
The SplashNavigator will render the SplashScreen. It has initial state value isReady set to false, so it will render a loading text until the #ProductTour:key value from AsyncStorage is loaded (AsyncStorage is async function, u should not put it in your render function). It will then render your AppNavigator and render your EnableNotification as initial route.
class SplashScreen extends Component {
constructor() {
super(props);
this.state = {
isReady: false,
}
}
componentDidMount() {
AsyncStorage.getItem("#ProductTour:key").then(value => {
console.log(value);
// you will need to handle case when `#ProductTour:key` is not exists
this.setState({
isReady: true,
});
});
}
render() {
const { isReady } = this.state;
return (
<View style={{flex: 1}}>
{
isReady ?
<AppNavigator />
: <Text>Loading</Text>
}
</View>
);
}
}
Then on EnableNotificationScreen and CreateMessageScreen, change your navigate route function to use NavigationActions.reset from doc
Example:
import { NavigationActions } from 'react-navigation';
handleOnPressButton = () => {
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: "CreateMessage" })
]
});
this.props.navigation.dispatch(resetAction);
}
Yes in react native you can finish the current screen before navigating to new screen with the help of NavigationActions . Please refer this link -
http://androidseekho.com/others/reactnative/finish-current-screen-on-navigating-another-in-react-native/
SplashNavigator.js
const SplashNavigator = StackNavigator({
Splash: {
screen: SplashScreen,
navigationOptions: {
header: null}
}
}
});
Import StackActions and NavigationActions from react-navigation.
import { StackActions, NavigationActions } from 'react-navigation';
below code for performing Action
navigateToHomeScreen = () => {
const navigateAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: "HomeScreen" })],
});
this.props.navigation.dispatch(navigateAction);
}

Categories

Resources