React Navigation: Prevent multiple instances to navigate to same screen - react-native

Let's say I have this screen:
And when the user clicks on the white tooltip, it redirects to another screen. Sometimes the app lags a little bit, and clicking on the tooltip takes like ~2s to see the screen change. The problem is, during those 2s, the user taps again on this tooltip to make it happen.
And the result I get is that there are two instances of the new screen in my StackNavigator. What I mean is that I see my new screen, but when I click on "Back" I don't return to this 'Hitchhiking Map' screen, but to another instance of that same screen.
If I clicked 5 times on the callout during those 2s, then I need to click 5 times "Back" to return to the Map screen. Any way to prevent that? To put only one instance into the StackNavigator?
I am using React Navigation, more precisely a StackNavigator. Here's my code:
The "click on tooltip" part:
<MapView.Marker
onCalloutPress={() => this.props.navigation.navigate('spotDetails', { spotId: marker.id })}
/>
My screens:
const HMapNavigator = StackNavigator({
HMap: { screen: HMapViewContainer },
spotDetails: { screen: SpotDetailsViewContainer },
});

The issue of multiple navigations has been reported and there is more detail here.
react-navigation (v1.0.0-beta.7) is in beta and still being built, so until this feature is implemented, you will have to handle the debounce manually.
options
disable the button once the navigation starts
debouncing in the onPress logic or in the action if you are using redux
lodash provides a useful debounce utility, if you are looking for one.

There are many ways how to overcome double navigations.
My solution is adding a key property in navigate object:
this.props.navigation.navigate({ key: 'AnotherScreen', routeName: 'AnotherScreen', params: { ... }})}

Save if its opening to control the navigation
constructor (props) {
super(props)
this.state = {
opening: false
}
}
Create a function to control de navigation
_open (campaign) {
if (!this.state.opening) {
this.props.navigation.navigate('Campaign', {campaign})
this.setState({ opening: true })
setTimeout(() => {
this.setState({ opening: false })
}, 1000)
}
}
Call this func from your onpress
<TouchableHighlight underlayColor='transparent' onPress={this._open.bind(this, campaign)}>

In versions 1.x of React Navigation, if you specify an identifier the navigation action will be executed only once. E.g.:
navigate({ routeName: 'someScreen', key: 'someScreen', params: { someParam: 'someValue' } })
More information: https://gist.github.com/vonovak/ef72f5efe1d36742de8968ff6a708985
In versions 2.x and 3.x this is a default functionality.
More info: https://reactnavigation.org/docs/en/navigation-key.html

Related

Refresh inside function of react-navigation screen

Using react-navigation I am doing stuff inside one of screens, where I display items, and optionally delete them. When deleting, I need to update the screen, which I am having trouble with. I tried navigating to the same screen or updating useState hook variables, which should re-render the screen but dont. For now, I am refreshing it in the dumbest way: I delete the item, navigate to other screen, timeout 5ms and go back to the screen that I want to update.
function ListScreen({navigation}) {
<FlatList>
...stuff
<ListItem.Chevron onPress={ () => Alert.alert('Alert Title', 'Do you really want to delete this?',
[{ text: 'YES', onPress: () => deleteAndRefresh(id) }
/>
function deleteAndRefresh(id) {
deleteProduct(id); //database call
navigation.navigate('Home');
setTimeout(function () {
navigation.navigate('List');
}, 5);
}
}
How could I refresh the screen inside deleteAndRefresh more ... elegantly?
Did you try push ?
navigation.push('List');

Rendering a TabNavigator in Storybook

I want to use Storybook.js to mock up a page that utilizes createMaterialTopTabNavigator(). However I can't render the material Tab because the output of the function is not actually a react component. So when I directly tried <TabNavigator /> it threw an invariant violation that "navigation" was missing. The only work-around I found was using createAppContainer() around the TabNavigator. But I know that it is bad practice to use more than one navigator. Is there any way to render a react navigator element without needing the navigation container?
This is my current working code for the storybook:
const tabConfiguration = {
Old: {
screen: OldProductsScreen,
navigationOptions: {
title: "Old"
}
},
New: {
screen: NewProductsScreen,
navigationOptions: {
title: "New"
}
}
};
const TabNavigator = createMaterialTopTabNavigator(tabConfiguration);
storiesOf('Test', module)
.add('Initial', () => {
const TAB = createAppContainer(TabNavigator);
return (
<TAB / >
);
})
;
I know that it is bad practice to use more than one navigator. Is there any way to render a react navigator element without needing the navigation container?
It's bad practice to do that in an app. But since you're using Storybook where each container is in like its own independent app, this is totally fine.

Receiving wrong params when navigating to a route

I'm using react-navigation in my react native app.
In two different places I a have button to navigate to a route. One button also sends params and one button does not:
<Button onPress={() => { navigation.navigate('profile', { user: 'test' }) }} title="test" />
<Button onPress={() => { navigation.navigate('profile') }} title="test" />
In profile route I have a componentWillMount function to set state according to received params:
componentWillMount() {
const { params } = this.props.navigation.state;
if (params && params.hasOwnProperty('profile')) {
this.setState({
profile: params.profile
});
}
}
The problem is that if I press the first button (the one that sends params), then navigate back to the same view and press the second button, I'm still receiving the same params, even though the second button isn't sending any!
Not by my computer so I can't test.
But what if you navigate with the second button first? Do you still get the params passed to the page?
Also you dont mention what type navigation you sre navigating to. A stack, drawer or tab. Add some more info and I'll try to come back to you tomorrow.
But my impression is that you are navigating to the same page the second time and that page is already mounted. And hence not rerendered. Have you tried with componentDidUpdate an passing no variables on the second button?
This is the change I suggest in our discussion:
if (params && params.hasOwnProperty('profile')) {
this.setState({
profile: params.profile
});
else if (params && params.hasOwnProperty('profile')){
this.setState({
profile: null
});
}

Custom backButtonIcon for NavigatorIOS

This question is related to
https://github.com/facebook/react-native/issues/1383
Is there anyway to have a custom back button icon / title with NavigatorIOS?
backButtonIcon, backButtonTitle is not working as expected. The left arrow is always visible. New routes with custom backButtonIcon or backButtonTitle doesnt show
this.props.navigator.push({
title: 'Next',
component: Next,
backButtonIcon: require('./../img/Back.png')
})
Use LeftButtonIcon instead of backButtonIcon like this;
this.props.navigator.push({
title: '详情',
component: PageList,
leftButtonIcon: {uri: 'nav-back'},
onLeftButtonPress: () => {
ViewController.toggleTabBar(true);
this.props.navigator.pop();
},
passProps: {
data: data
}
});
The letButtonIcon comes from Native App.
You can easily achieve this using the Navigator component. Navigator is the JS implementation of a navigator. You can customize every aspect of it and you can use it in both iOS and Android. Further more, NavigatorIos is not maintained as much as Navigator. More info here: https://facebook.github.io/react-native/docs/navigator-comparison.html#content
And here:
https://facebook.github.io/react-native/docs/navigator.html#content

Triggering an action of a different page using NavigatorIOS Buttons

I'm making an app with two Buttons in the Navigator in the Homepage. I have also used React Native Side Menu. (https://github.com/react-native-fellowship/react-native-side-menu)
From the left button of the NavigatorIOS, I want to trigger the toggle() method of the Side Menu. How am I to accomplish this?
<NavigatorIOS
style={styles.navigationContainer}
ref="nav"
initialRoute={{
title: "Home",
component: Home,
ref:'home',
rightButtonTitle: 'Items',
leftButtonTitle: 'Menu',
onLeftButtonPress: () => {
//menuActions.toggle(); HERE IS THE PROBLEM
},
onRightButtonPress: () => {
this.refs.nav.navigator.push({
title: "Items",
component: Items
});
}
}} />
The code and the elements for the Sidemenu is in the Home file. How to access this from this code, which is in the index.ios.js?
Thanks in advance.
I'm afraid that's not enough to make a conclusion. First of all, I need to see, how you compose your components. menuActions are available only for children components thru the context. Based on this code fragment I can't see how you make do it.
My suggestions:
- Check if you NavigatorIOS component is a children of your SideMenu component
- If you don't want to use NavigatorIOS as a children of a SideMenu, you may need something like a global store (you can use redux, for example) which will reflect a state of the SideMenu. Subscribing to this store from the right place (in any children of SideMenu), you'll be able to trigger menuActions from the place it'll be accessible.