In React Native, the method of BackHandler does not work until back root route - react-native

when I press physical return button on my phone the log have no output until back root route
componentWillMount(){
BackHandler.addEventListener('hardwareBackPress', this._onBackAndroid)
}
_onBackAndroid = () => {
console.log(this.props.navigation.state.routeName)
if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
return false;
}
this.lastBackPressed = Date.now();
toastShort('Press Again Exit App');
return true;
};
componentWillUnmount(){
BackHandler.removeEventListener('hardwareBackPress', this._onBackAndroid)
}

By design, react-navigation takes full control of the back button unless you handle the state yourself (by using something like Redux).
You can take a look at this issue, which is referencing a similar situation.

Related

React Native, how to update async-storage immediately?

I'm making a simple drinking game. When a playing card shows, it's corresponding rule shows below it. I have a settings.js file where the rules are, and the user can see and modify the rules, and they update on the game.js file. I'm using async-storage to store the rules.
I wanted to add a button in the settings.js file, which would return the original rules when pressed. The only problem is, that the original rules don't update immediately on the settings screen. When the button is pressed the original rules do update on the game, but they update on the settings screen only when the user goes back in the game and then back in the settings screen.
The code for updating the rules:
initialState = async () => {
try {
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
...
await AsyncStorage.setItem('rule13', 'theoriginalrule13')
catch(err) {
console.log(err)
}
}
I have the following line of code to update the async-storage when the screen is entered, but as said, it only works when the screen is re-entered:
componentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
this.getData();
});
}
To answer your question here, not in a comment.
Try this :
componentDidMount() {
const { navigation } = this.props;
this.getData();
this.focusListener = navigation.addListener('didFocus', () => {
this.getData();
});
}
I would suggest you to use ,
State driven UI
means your ui will change only when state is changed , now suppose you are changing your asyncStorage, using
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
so I would suggest your state will also update after updating your aysncStorage like.
//Initial state
this.state = { score: 0 };
async storeValues(){
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
let newScoreValue = await AsyncStorage.getItem('rule1')
this.setState({score:newScoreValue})
}
// UI will be like
render(){
return(<Text>{this.state.score}</Text>)
}

React Native, store information before app exits

Is this at all possible? I'm currently using react-native-track-player to stream audio files and I would love to be able to store the last position when my users exit the app and resume when they re-open (e.g. similar to how Spotify works)
Right now I'm tracking this info via a simple interval:
this.keepTime = setInterval(async () => {
const state = await TrackPlayer.getState()
if (state == TrackPlayer.STATE_PLAYING) {
const ID = await TrackPlayer.getCurrentTrack()
const position = await TrackPlayer.getPosition()
await AsyncStorage.setItem(ID, String(position))
}
}, 10000)
Problem is, I need to clear the interval when my app moves to the background or else it will crash. I would also much rather only need to call this code once as opposed to periodically if that is possible.
I know I could use headless JS on android but the app is cross platform, so my iOS user experience would be lesser.
Any suggestions?
I think you can use componentWillUnmount() function for this.
You could add a listener to get the App State and then log the position when it goes to background.
class App extends React.Component {
state = {
appState: AppState.currentState
}
componentDidMount() {
AppState.addEventListener('change', this.handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
this.saveTrackPosition();
}
handleAppStateChange = (nextAppState) => {
if (nextAppState.match(/inactive|background/) && this.state.appState === 'active') {
this.saveTrackPosition();
}
this.setState({appState: nextAppState});
}
saveTrackPosition = () => {
if (state == TrackPlayer.STATE_PLAYING) {
const ID = await TrackPlayer.getCurrentTrack()
const position = await TrackPlayer.getPosition()
await AsyncStorage.setItem(ID, String(position))
}
}
}

How to set dashboard as first screen after login in react-native

I am using react-native where my first screen is Welcome screen and I want to set dashboard on my first screen when the user is login.
Here is my code:
componentWillMount(){
var self = this;
AsyncStorage.getItem(AppStrings.contracts.IS_LOGGED_IN).then((json) =>{
try{
var userDetail = JSON.parse(json);
if(userDetail.isLoggedIn != undefined && userDetail.isLoggedIn == true){
Actions.dashboard();
}
}catch(e){
}
})
}
I set this code on the Welcome screen and its working fine in IOS. But in android issue is it shows the Welcome screen for 5 to 10 seconds before going to dashboard screen when the user is login.
Here I am using react-native-router-flux for the navigation bar.
Because AsyncStorage.getItem() is asynchronous, your render() function is being called BEFORE it has been fulfilled.
So the flow of your application is:
Call componentWillMount()
AsyncStorage.getItem()
render() - This is where you see your Welcome Screen for 5-10 seconds
AsyncStorage has been fulfilled - .then() and then the User gets redirected to the dashboard.
I would set an isLoaded flag in your state:
constructor() {
super();
this.state = {
isLoaded: false,
}
}
Then inside of your componentWillMount() function, set the value to true once AsyncStorage has fulfilled its Promise.
try {
var userDetail = JSON.parse(json);
if(userDetail.isLoggedIn != undefined && userDetail.isLoggedIn == true){
Actions.dashboard();
}
this.setState({ isLoaded: true });
}
And finally, I would add some sort of loading indicator inside of render() to show the User that your application is still performing some logic.
render() {
if(this.state.isLoading) {
return <Text>I am loading</Text>
} else {
return ...
}
}

How to avoid navigating to other screen multiple times

When press on any button on my React Native App to navigate to a different screen multiple times, then it will redirected to the next screen multiple times.
My sample code is:
// This is my button click event
myMethod()
{
this.props.navigation.navigate("ScreenName")
}
I am using react-navigation to navigate through my app.
How can I fix this behaviour?
I think there are a few ways this could be done. Perhaps recording when the navigation has occurred and preventing it from navigating multiple times.
You may also want to consider resetting hasNavigated after an amount of time etc as well.
// Somewhere outside of the myMethod scope
let hasNavigated = false
// This is my button click event
myMethod()
{
if (!hasNavigated) {
this.props.navigation.navigate("ScreenName")
hasNavigated = true
}
}
This react-navigation issue contains a discussion about this very topic, where two solutions were proposed.
The first, is to use a debouncing function such as Lodash's debounce that would prevent the navigation from happening more than once in a given time.
The second approach, which is the one I used, is to check on a navigation action, whether it is trying to navigate to the same route with the same params, and if so to drop it.
However, the second approach can only be done if you're handling the state of the navigation yourself, for example by using something like Redux.
Also see: Redux integration.
One of solution is custom custom components with adds debounce to onPress:
class DebounceTouchableOpacity extends Component {
constructor(props) {
super(props);
this.debounce = false;
}
_onPress = () => {
if (typeof this.props.onPress !== "function" || this.debounce)
return;
this.debounce = true;
this.props.onPress();
this.timeoutId = setTimeout(() => {
this.debounce = false;
}, 2000);
};
componentWillUnmount() {
this.timeoutId && clearTimeout(this.timeoutId)
}
render() {
const {children, onPress, ...rest} = this.props;
return (
<TouchableOpacity {...rest} onPress={this._onPress}>
{children}
</TouchableOpacity>
);
}
}
another: wrap onPress function into wrapper with similar behavior
const debounceOnPress = (onPress, time) => {
let skipCall = false;
return (...args) => {
if (skipCall) {
return
} else {
skipCall = true;
setTimeout(() => {
skipCall = false;
}, time)
onPress(...args)
}
}
}

While coming back from react navigation componentWillMount doesn't get called

I have used react-navigation and on clicking hardware back button in android, I come back to previous component but componentWillMount doesn't get called. How do I ensure that componentWillMount is called?
componentWillMount will not trigger when you entering new screen / back to the screen.
my solution is using event navigator handler
https://wix.github.io/react-native-navigation/#/screen-api?id=listen-visibility-events-in-onnavigatorevent-handler
you can implement your 'componentWillMount' codes while 'willAppear' event id triggered, see this implementation:
export default class ExampleScreen extends Component {
constructor(props) {
super(props);
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
onNavigatorEvent(event) {
switch(event.id) {
case '`willAppear`':
// { implement your code on componentWillMount }
break;
case 'didAppear':
break;
case 'willDisappear':
break;
case 'didDisappear':
break;
case 'willCommitPreview':
break;
}
}
}
Does this answer from #bumbur help you? It defines a global variable that tracks if nav state has changed. You could insert a piece of code to see if you're in the specific tab that you are interested in. With that you could trigger a call to componentWillMount() ?
If you don't want to use redux, this is how you can store globally
information about current route, so you can both detect a tab change
and also tell which tab is now active.
https://stackoverflow.com/a/44027538/7388644
export default () => <MyTabNav
ref={(ref) => { this.nav = ref; }}
onNavigationStateChange={(prevState, currentState) => {
const getCurrentRouteName = (navigationState) => {
if (!navigationState) return null;
const route = navigationState.routes[navigationState.index];
if (route.routes) return getCurrentRouteName(route);
return route.routeName;
};
global.currentRoute = getCurrentRouteName(currentState);
}}
/>;