Is it possible to intercept the back action in react native on any route?
Lets say I am on a page and I want navigate a user on a different route when he/she performs a back action.
or even a popup confirm.
You can use React's [BackHandler][1] for instance
// import the back handler from react-native
import { BackHandler } from 'react-native'
// fire the listener on every render (including the componentWillUnmount)
useLayoutEffect(() => {
BackHandler.addEventListener(
"hardwareBackPress",
{
console.warn('about to go back....')
}
);
}, [])
[1]: https://reactnative.dev/docs/backhandler
Related
I do not want a user to click browser back button and forward button from my page because my page loads stored sessions every time. If user is clicking on these buttons then i simply want him to navigate to the homepage.
For security purpose, sessions are being maintained by the backend APIs so if user will fluctuate on browser's forward and back buttons then it will cause session disruption.
I have searched everywhere and tried lots of things but no solution is compatible with the v6 of react-router-dom. There must be solution for this. Please help me out in getting the same.
this was my question recently. I searched a lot but have nothing found to do this work with react-router v6. but you can handle browser back and forward buttons using window 'popstate' event. I wrote a hook for connecting, detect and handle this event(useBackButton):
import { useEffect, useState } from "react";
const useBackButton = (callback) => {
const [isBack, setIsBack] = useState(false);
const handleEvent = () => {
setIsBack(true);
callback();
window.history.go(1);
};
useEffect(() => {
window.addEventListener("popstate", handleEvent);
return () => window.removeEventListener("popstate", handleEvent);
});
return isBack;
};
export default useBackButton;
you can write your function that does your desired work and send it to this hook. then call this hook in every component that you want.
I have something that looks like this:
import React from 'react';
import PropTypes from 'prop-types';
import { Prompt } from 'react-router-dom';
const ConfirmationDialog = (props) => {
if (props.navigatingAway) {
window.onbeforeunload = () => true;
} else {
window.onbeforeunload = null;
}
return (
<Prompt
when={props.navigatingAway}
message="Are you sure?"
/>
);
};
ConfirmationDialog.propTypes = {
navigatingAway: PropTypes.bool.isRequired,
};
export default ConfirmationDialog;
I'm trying to figure out the best way to extend this so that navigatingAway actually does something. I don't understand what criteria to use for it, necessarily, just that it should trigger the confirmation window when:
a user changes the URL and attempts to navigate away
a user clicks on a link
a user refreshes the browser
What would be the best way to check for URL changes for when?
You don't need to come up with a way to 'detect' when one of your scenarios is occurring.
a user changes the URL and attempts to navigate away
a user refreshes the browser
These are already handled by virtue of assigning a callback to onbeforeunload.
a user clicks on a link
This is already handled by virtue of Prompt being rendered, if you're handling navigation with react-router.
props.navigatingAway, then, would be better named props.shouldPreventNavigation or something along those lines, because it should signal IF you should prevent navigating, not whether you ARE navigating.
For example, if you ALWAYS want a prompt to appear before navigation while ConfirmationDialog is mounted, then props.shouldPreventNavigation should just always be true, and you're done. A common use case would be to set it to true if there is unsaved data in a form.
From the docs for Prompt:
Instead of conditionally rendering a <Prompt> behind a guard, you can always render it but pass when={true} or when={false} to prevent or allow navigation accordingly.
To illustrate this, the following two snippets are functionally equivalent, apart from performance and such:
render() {
return (
<Prompt
when={this.props.navigatingAway}
message="Are you sure?"
/>
)
}
render() {
if (this.props.navigatingAway) {
return (
<Prompt
when={true}
message="Are you sure?"
/>
)
}
return null;
}
If Prompt isn't working properly out of the box when when={true}, then it could be that your routing isn't being properly managed by react-router.
As a side note, make sure you consider what happens with window.onbeforeunload if, for example, your ConfirmationDialog unmounts while it has a callback assigned. Use the appropriate lifecycle methods to manage this, or things are gonna get weird when you're testing this.
I'm building a App with react-native v0.44.0 using redux v5.0.5 and react-navigation v1.0.0-beta.11. The routing is done with nested navigators, one main StackNavigator and DrawerNavigator.
I'm handling all navigation events in a navigation reducer, also the hardware back press on Android using BackHandler. Now comes the weird part (for me), I've implemented the BackHandler event handlers like so:
import { BackHandler, Modal, View } from 'react-native';
import { NavigationActions } from 'react-navigation';
import { HARDWARE_BACK_PRESS } from '../helpers/NavTypes';
constructor(props) {
super(props);
this.handleBack = this.handleBack.bind(this);
}
componentWillMount() {
BackHandler.addEventListener(HARDWARE_BACK_PRESS, this.handleBack);
}
componentWillUnmount() {
BackHandler.removeEventListener(HARDWARE_BACK_PRESS, this.handleBack);
}
handleBack() {
const navAction = NavigationActions.back();
this.props.navigation.dispatch(navAction);
return true;
}
In my navigation reducer I'm handling the Navigation/BACK action type and keep track of my state. Now, when I'm pressing the hardware back button on my Android device or in the emulator, I can see thanks to redux-logger and the React Native debugger that the navigation action is dispatched correctly and the previous shown screen appears, but the app closes anyway. This happens also when I alter the handleBack method to something like this:
handleBack() {
return true;
}
Every time the hardware back button is pressed, the App still closes. I did some step-debugging innode_modules/react-native/Libraries/Utilities/BackHandler.android.js, inside of RCTDeviceEventEmitter.addListener I can see that my event listeners are registered and invokeDefault is set to true in the loop. addListener is exited but the App still closes. Does anyone know if there is some point there react-navigation and redux are overriding the behaviour of the hardware back button at some top level I'm not aware of?
I've setup a second plain RN project without react-navigation and redux, implementing the same BackHandler event listeners (also returning true) and the app won't close. So, right now this leaves me a bit puzzled.
I'm using react-navigation and I also handle the os back button. It works fine for me.
May be you can try this out. Note that handleBack must return true if you are performing any other task than closing the app. If not it will close the app immediately.
componentWillMount() {
BackHandler.addEventListener(HARDWARE_BACK_PRESS, () => { return this.handleBack.bind(this)() });
}
I had the same problem, returning true wouldn't update. Running react-native run-android again after the changes fixed the problem for me.
In my React/Redux based application, I have implemented logout like following
In reducers/index.js where I do combineReducers, I have created an app level reducer called appReducer. There I check for LOGOUT action and then return undefined.
All this works fine. What I want to do is, that for LOGOUT action, I also want to clear localStorage and redirect to login page. Please note that I want to redirect native browser way, not using react-router. If I do window.location = '/'. First its detected by react-router and I see login page for a bit and then it refreshes which is a bit odd.
Is there a way to prevent react-router from being notified on location change!?
You cannot prevent it completely but you can control it with the access to
history.listen function.
With React-Router 4 you can wrap top level components using the HOC withRouter.
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
This allows accessing this.props.history and controlling it
class App extends Component {
constructor(props) {
super(props);
this.props.history.listen((location, action) => {
//here you can control the location change
});
}
render() {
return (
</div>
);
}
}
And you also have the listenBefore event on the history object and you can use this event to control the navigation and add your own custom navigation logic:
history.listenBefore( (location, done) => doSomething(location).then(done) )
I have a simple display which is based on some async data.
My code is as such:
componentDidMount() {
asynFunctionCall(result => {
this.setState({stateVariable: result});
});
}
And I use the state variable stateVariable to decide whether or not to show a <Text> component:
render() {
return (
<View>
{this.state.stateVariable &&
<Text> Special Message </Text>
}
</View>
);
}
What I want is for asyncFunctionCall to be run whenever the app comes to the foreground so that the user can leave the app, do something (which may affect the result of that async call), and come back to the app and have the display updated appropriately.
This seems like a standard app behavior, and I wonder if React Native's lifecycle API has a standard way to support it, but I'm not finding it.
How can I achieve this behavior, with a lifecycle function or otherwise?
Yes. you can use the AppState https://facebook.github.io/react-native/docs/appstate.html API.
I would suggest to add this somewhere at the start of the app and coordinate your views accordingly.
AppState should work mostly for iOS, but for Android background activity is triggered anytime a native module outside of your own code is triggered, e.g capturing a photo. It is way better to detect home or recent app button clicks. This is because fragments within your app from other apps like social media or photos will also trigger background state, which you don't want because they are still in the app adding a photo to a profile from the camera etc. You can easily detect home and recent app button clicks on Android with react-native-home-pressed. This library simply exposes the android button events.
First install the library with npm i react-native-home-pressed --save and then link it react-native link. Then rebuild your app and add the following snippet.
import { DeviceEventEmitter } from 'react-native'
class ExampleComponent extends Component {
componentDidMount() {
this.onHomeButtonPressSub = DeviceEventEmitter.addListener(
'ON_HOME_BUTTON_PRESSED',
() => {
console.log('You tapped the home button!')
})
this.onRecentButtonPressSub = DeviceEventEmitter.addListener(
'ON_RECENT_APP_BUTTON_PRESSED',
() => {
console.log('You tapped the recent app button!')
})
}
componentWillUnmount(): void {
if (this.onRecentButtonPressSub) this.onRecentButtonPressSub.remove()
if (this.onHomeButtonPressSub) this.onHomeButtonPressSub.remove()
}
}