Unable to navigate to previous page using device back button (Backhandler) - android

I have 2 Screens, Screen A and Screen B. I am including Screen B in Screen A.
When I am in the Screen B, I am able to navigate back from the device back button to Screen A.
But when I go the Screen B and navigate back to Screen A, the device back button is not working in Screen A.
Screen A
import React, { Component } from 'react';
import { View, Button, BackHandler } from 'react-native';
import B from './B';
export default class A extends Component {
state = {
isB: false,
}
_onBack = () => {
this.props.navigation.goBack();
return true;
}
componentDidMount(){
BackHandler.addEventListener('hardwareBackPress', this._onBack)
}
ComponentWillUnmount(){
BackHandler.removeEventListener('hardwareBackPress', this._onBack)
}
render(){
<>
{
isB ?
<B/>
:
<View>
<Button onPress={() => this.setState({ isB: true})}>Edit</Button>
</View>
}
</>
}
}
Screen B
import React, { Component } from 'react';
import { View, BackHandler } from 'react-native';
export default class B extends Component {
_onBack = () => {
this.props.navigation.goBack();
return true;
}
componentDidMount(){
BackHandler.addEventListener('hardwareBackPress', this._onBack)
}
ComponentWillUnmount(){
BackHandler.removeEventListener('hardwareBackPress', this._onBack)
}
render(){
<View>
</View>
}
}

This is from official docs:
The event subscriptions are called in reverse order (i.e. the last
registered subscription is called first).
If one subscription returns true, then subscriptions registered earlier will not be called.
If no subscription returns true or none are registered, it programmatically invokes the default back button functionality to exit
the app.
So when you return true in _onBack , this will be the only callback that will be called in back button press.
you have to do what you want in this function, if you want to navigate back to the screen you were before(you probably using a navigation library like react-navigation or router-flux) put the proper code in this funtcion like below:
_onBack = () => {
Actions.pop() // rn-router-flux
navigation.goBack(); // rn-navigation
return true;
}

Related

Override back button wix react native navigation V2?

I am currently transitioning from Wix RNN V1 to V2, and so far I've managed to find the appropriate replacement APIs, except for overriding the back button on Android.
In V1 we could pass the overrideBackPress: true attribute, and then handle back button presses manually on the cooresponding screen.
However, in V2 I've found no such replacement, and the only topics I could find were this thread:
https://github.com/wix/react-native-navigation/issues/4217
I've implemented the suggestions there, but Wix navigation is still automatically closing screens even though it should be overwritten.
Any known a solution for this?
I had the same issue and the only way i could override the backpress behavior on both platforms is to replace the left back button with custom button and use the BackHandler of react native for the hardware button in Android. The code is as below.
Component A
//Navigate to component B from A
Navigation.push(this.props.componentId, {
component: {
name: 'ComponentB',
options: {
topBar: {
leftButtons: [{
id: 'backPress',
text: 'Back',
icon: require('backbutton.png')
}]
},
}
}
});
Component B
import React, { PureComponent } from 'react';
import { View, BackHandler } from 'react-native';
import { Navigation } from 'react-native-navigation';
export default class ComponentB extends PureComponent {
constructor(props) {
super(props);
Navigation.events().bindComponent(this);
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
}
navigationButtonPressed({ buttonId }) {
switch (buttonId) {
case 'backPress': {
this.handleBackPress();
break;
}
}
}
handleBackPress = () => {
//Custom logic
//Go back if required
Navigation.pop(this.props.componentId)
//Stop the default navigation
return true;
};
//Render component
render() {
return (<View></View>);
}
}
You can use registerScreenPoppedListener:
Navigation.events().registerScreenPoppedListener((event) => {
if (event.componentId === "my-screen-id") {
// do something
}
});

How to load the same last component class in react native using react navigation?

My application scenario is like, let say you have three components:
class ComponentOne extends Component {
render() {
return (
<View>
<Text>Component One</Text>
<Button
title='Go To Component Two'
onPress={() => this.props.navigation.navigate('two')}/>
</View>
);
}
}
class ComponentTwo extends Component {
render() {
return (
<View>
<Text>Component Two</Text>
<Button
title='Go To Component Three'
onPress={() => this.props.navigation.navigate('three')}/>
</View>
);
}
}
class ComponentThree extends Component {
render() {
return (
<View>
<Text>Component Three</Text>
<Button
title='Go To Component One'
onPress={() => this.props.navigation.navigate('one')}/>
</View>
);
}
}
export default createStackNavigator({one: ComponentOne,two: ComponentTwo,three: ComponentThree});
Now when I load the app the ComponentOne will be loaded, inside the ComponentOne when I click on the button Go To Component Two it will navigate me to the ComponentTwo, inside ComponentTwo when I click on the button Go To Component Three it will navigate me to the ComponentThree and so on. Now let say I am in ComponentTwo and on the same time I close the application in the phone and I open the app switcher and clean all the running apps and load the same app again, so, it will be again loaded with ComponentOne.
My question is how to program the react navigation to continue from the same component where last time I left, even after cleaning the app from a background (cleaning all apps in app switcher)?
Is there any builtin way in react navigation to do this? Can anyone tell? Examples will be more appreciated. Thanks!
All Navigators have onNavigationStateChange where you can handle the navigation state changing. Example code:
import React from 'react';
import { AsyncStorage } from 'react-native';
import { createStackNavigator, NavigationActions } from 'react-navigation';
const Navigator = createStackNavigator({
ComponentOne: {
screen: ComponentOne,
},
ComponentTwo: {
screen: ComponentTwo,
},
ComponentThree: {
screen: ComponentThree,
},
}, {
initialRouteName: 'ComponentOne',
});
class App extends Component {
constructor(props) {
this.navigator = React.createRef();
}
componentDidMount() {
try {
// Retrieve the last route
const value = AsyncStorage.getItem('lastNavigationRoute').then((result) => {
if (result) {
this.navigator.current.dispatch(NavigationActions.navigate({
routeName: result,
}));
}
});
} catch (e) {
// handle the error
}
}
handleStateChange = (previousState, nextState) => {
// Here we get the Navigate action type only
const navigateAction = NavigationActions.navigate({ routeName: 'dummyRoute' });
if (action.type === navigateAction.type) {
try {
// Saving the last route
AsyncStorage.setItem('lastNavigationRoute', nextState.routeName);
} catch (e) {
// handle the error
}
}
}
render() {
// You could also set a state with loader to handle loading from AsyncStorage
return (
<Navigator onNavigationStateChange={this.handleStateChange} ref={this.navigator} />
);
}
}
How it works:
On every navigation state changing you also save the last routeName
from Navigate action
When component did mount, you check for saved
route in AsyncStorage
If there is a route, you dispatch the navigate action (it's possible to implement replace action as well)
Hope it helps.
i dont think that there is a solution directly using react-navigation.
What i think you could do is to save a value of the current screen to the storage of the phone and then use this value on app start to detect which screen to show

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.

`componentDidMount()` function is not called after navigation

I am using stackNavigator for navigating between screens. I am calling two API's in componentDidMount() function in my second activity. When i load it first time, it gets loaded successfully. Then i press back button to go back to first activity. Then, if i am again going to second activity, the APIs are not called and I get render error. I am not able to find any solution for this. Any suggestions would be appreciated.
If anyone coming here in 2019, try this:
import {NavigationEvents} from 'react-navigation';
Add the component to your render:
<NavigationEvents onDidFocus={() => console.log('I am triggered')} />
Now, this onDidFocus event will be triggered every time when the page comes to focus despite coming from goBack() or navigate.
If the upvoted syntax that uses NavigationEvents component is not working, you can try with this:
// no need to import anything more
// define a separate function to get triggered on focus
onFocusFunction = () => {
// do some stuff on every screen focus
}
// add a focus listener onDidMount
async componentDidMount () {
this.focusListener = this.props.navigation.addListener('didFocus', () => {
this.onFocusFunction()
})
}
// and don't forget to remove the listener
componentWillUnmount () {
this.focusListener.remove()
}
The React-navigation documentation explicitly described this case:
Consider a stack navigator with screens A and B. After navigating to
A, its componentDidMount is called. When pushing B, its
componentDidMount is also called, but A remains mounted on the stack
and its componentWillUnmount is therefore not called.
When going back from B to A, componentWillUnmount of B is called, but
componentDidMount of A is not because A remained mounted the whole
time.
Now there is 3 solutions for that:
Subscribing to lifecycle events
...
React Navigation emits events to screen components that subscribe to
them. There are four different events that you can subscribe to:
willFocus, willBlur, didFocus and didBlur. Read more about them in the
API reference.
Many of your use cases may be covered with the withNavigationFocus
higher-order-component, the <NavigationEvents /> component, or the
useFocusState hook.
the withNavigationFocus
higher-order-component
the <NavigationEvents />
component
the useFocusState hook (deprecated)
withNavigationFocus
higher-order-component
import React from 'react';
import { Text } from 'react-native';
import { withNavigationFocus } from 'react-navigation';
class FocusStateLabel extends React.Component {
render() {
return <Text>{this.props.isFocused ? 'Focused' : 'Not focused'}</Text>;
}
}
// withNavigationFocus returns a component that wraps FocusStateLabel and passes
// in the navigation prop
export default withNavigationFocus(FocusStateLabel);
<NavigationEvents /> component
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const MyScreen = () => (
<View>
<NavigationEvents
onWillFocus={payload => console.log('will focus', payload)}
onDidFocus={payload => console.log('did focus', payload)}
onWillBlur={payload => console.log('will blur', payload)}
onDidBlur={payload => console.log('did blur', payload)}
/>
{/*
Your view code
*/}
</View>
);
export default MyScreen;
useFocusState hook
First install library yarn add react-navigation-hooks
import { useNavigation, useNavigationParam, ... } from 'react-navigation-hooks'
function MyScreen() { const focusState = useFocusState(); return <Text>{focusState.isFocused ? 'Focused' : 'Not Focused'}</Text>; }
Here is my personal solution, using onDidFocus() and onWillFocus() to render only when data has been fetched from your API:
import React, { PureComponent } from "react";
import { View } from "react-native";
import { NavigationEvents } from "react-navigation";
class MyScreen extends PureComponent {
state = {
loading: true
};
componentDidMount() {
this._doApiCall();
}
_doApiCall = () => {
myApiCall().then(() => {
/* Do whatever you need */
}).finally(() => this.setState({loading: false}));
};
render() {
return (
<View>
<NavigationEvents
onDidFocus={this._doApiCall}
onWillFocus={() => this.setState({loading: true})}
/>
{!this.state.loading && /*
Your view code
*/}
</View>
);
}
}
export default MyScreen;
Solution for 2020 / React Navigation v5
Inside your screen's ComponentDidMount
componentDidMount() {
this.props.navigation.addListener('focus', () => {
console.log('Screen.js focused')
});
}
https://reactnavigation.org/docs/navigation-events/
Alternatively: Put the addListener method in constructor instead to prevent duplicated calls
React-navigation keeps the component mounted even if you navigate between screens. You can use the component to react to those events :
<NavigationEvents
onDidFocus={() => console.log('hello world')}
/>
More info about this component here.
According to react-navigation docs we can use as below
componentDidMount () {
this.unsubscribe= this.props.navigation.addListener('focus', () => {
//Will execute when screen is focused
})
}
componentWillUnmount () {
this.unsubscribe()
}
Similar to vitosorriso`s answer but should changed didFocus to focus according to docs
You want to perform something after every time navigating to a component using drawernavigator or stacknavigator ; for this purpose NavigationEvents fits better than componentDidMount .
import {NavigationEvents} from "react-navigation";
<NavigationEvents onDidFocus={()=>alert("Hello, I'm focused!")} />
Note : If you want to do the task every time after focusing on a component using drawer navigation or stack navigation then using NavigationEvents is better idea. But if you want to do the task once then you need to use componenetDidMount .
//na pagina que você quer voltar
import {NavigationEvents} from 'react-navigation';
async atualizarEstado() {
this.props.navigation.setParams({
number: await AsyncStorage.getItem('count'),
});}
render() {
return (
<View style={styles.container}>
<NavigationEvents onDidFocus={() => this.atualizarEstado()} />
</View>
);
}
I have face this issue, the problem is when you navigate a page, the first time it call constructor, componentWillmount, render componentDidmount,
but in second time when navigate to the same page it only call render, so if you do any API call or something from componentDidmount it would not be called,
and also componentWillunmount never called.
You can use this method, if you are using react-navigation 5.x with class component, it can solve your problem.
for every class component page add this method and call this method once from the constructor
constructor(props) {
super(props);
this.state = {
...
};
...
this.navigationEventListener(); // call the function
}
navigationEventListener = () => { // add this function
let i = 0;
const initialState = this.state;
this.props.navigation.addListener('focus', () => {
if (i > 0) {
this.setState(initialState, () => {
//this.UNSAFE_componentWillMount(); // call componentWillMount
this.componentDidMount(); // call componentDidMount
});
}
});
this.props.navigation.addListener('blur', () => {
this.componentWillUnmount(); //call componentWillUnmount
++i;
});
}
https://reactnavigation.org/docs/navigation-events/
useEffect(() => {
const unsubscribe = props.navigation.addListener('focus', () => {
// do something
// Your apiCall();
});
return unsubscribe;
}, [props.navigation]);
In React, componentDidMount is called only when component is mounted.I think what you are trying to do is call your API on going back in StackNavigator. You can pass a callback function as parameter when you call navigate like this on Parent Screen:
navigate("Screen", {
onNavigateBack: this.handleOnNavigateBack
});
handleOnNavigateBack = () => {//do something};
And on Child Screen
this.props.navigation.state.params.onNavigateBack();
this.props.navigation.goBack();

Categories

Resources