How to skip back/modify navigation - react-native

I have a stackNavigation which works great. 3 screens : DeviceList / DeviceDetail / DevideAdd. The normal paths is (1) DeviceList > DeviceDetail or (2) DeviceList > DeviceAdd > DeviceDetail.
But when user use the path (2), I want that the back button of the DeviceDetail screen go to DeviceList. Or for the moment, it's going to DevideAdd.
Here is my navigation:
const DeviceStackNavigator = createStackNavigator({
DeviceList: {
screen: DeviceList,
},
DeviceDetail: {
screen: DeviceDetail,
},
DeviceAdd: {
screen: DeviceAdd,
},
});
How to achieve it ?

The key attribute 'goBack()' is a dynamically generated string that is created each time you navigate to a new 'react-navigation' path.
So if you want to go from DeviceDetail to DeviceList, what you have to do is to pass the key of DeviceAdd down to DeviceDetail, and then call goBack() with the key.
DeviceAdd.js
render() {
const { state, navigate } = this.props.navigation;
return (
<View>
<Button title="Go to DeviceDetail" onPress={ () => {
navigate('DeviceDetail', { go_back_key: state.key });
}} />
</View>
);
}
DeviceDetail.js
render() {
const { state, goBack } = this.props.navigation;
const params = state.params || {};
return (
<View>
<Button title="Back to DeviceList" onPress={ () => {
goBack(params.go_back_key);
}} />
</View>
);
}

Related

Cannot call Element Props inside React Navigation Stack.Navigator

I am using React-Navigation v5 with Redux. I like to call my logOut action creator to trigger my log out function from my headerRight.
However, I can only access logOut from inside Home element and not inside HomeStack.
One idea I have is to also wrap my HomeStack with connect. I haven't tried it yet to know whether it can work or not. Even should it work, this isn't my preferred solution because i feel it makes my code very verbose
Anyone has a solution on how to access my logOut function from within my HomeStack? Thanks in advance
const Home = props => {
const { loading, error, data, subscribeToMore } = useQuery(FIND_BOOKS_QUERY);
console.log("home props", props);
return (
<View>
<Text> Welcome {props.user && props.user.name} </Text>
{loading && <Text>Loading...</Text>}
{error && <Text>Error: {error.message}</Text>}
{data &&
data.findBooks.map((x, index) => {
return (
<View key={index}>
<Text>
{x.title} - {x.author}
</Text>
</View>
);
})}
</View>
);
};
const HomeContainer = connect(
state => {
// console.log("home state", state);
return {
user: state.auth.user
};
},
{ logOut }
)(Home);
export const HomeStack = props => {
console.log("home stack props", props);
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeContainer}
options={{
headerRight: () => {
return (
<TouchableOpacity
onPress={() => {
// props.logOut // - cannot access logOut here as HomeStack props does not have logOut
console.log("exit");
}}
>
<Text>Exit</Text>
</TouchableOpacity>
);
}
}}
/>
</Stack.Navigator>
);
};
Wrapping my HomeStack with connect works. Its able to make available logOut inside H which allows me to call logOut inside headerRight.
However, such a method will be verbose because I will need to connect both H and Home to redux. Is there a more elegant way? Thanks
const Home = props => {
const { loading, error, data, subscribeToMore } = useQuery(FIND_BOOKS_QUERY);
console.log("home props", props);
return (
<View>
<Text> Welcome {props.user && props.user.name} </Text>
{loading && <Text>Loading...</Text>}
{error && <Text>Error: {error.message}</Text>}
{data &&
data.findBooks.map((x, index) => {
return (
<View key={index}>
<Text>
{x.title} - {x.author}
</Text>
</View>
);
})}
</View>
);
};
const HomeContainer = connect(
state => {
// console.log("home state", state);
return {
user: state.auth.user
};
},
{ logOut }
)(Home);
export const H = props => {
console.log("home stack props", props);
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeContainer}
options={{
headerRight: () => {
return (
<TouchableOpacity
onPress={() => {
props.logOut(); // now works OK
console.log("exit");
}}
>
<Text>Exit</Text>
</TouchableOpacity>
);
}
}}
/>
</Stack.Navigator>
);
};
export const HomeStack = connect(
state => {
// console.log("home state", state);
return {
user: state.auth.user
};
},
{ logOut }
)(H);

React-Native-Navigation changing state in other screen with getParam()

I want to navigate to Decrease screen from Home screen and after entering a number into TextInput send it to change total in Home screen by using ```getParam('clickMe','changeTotal')`` but I couldn't find a way to pass number to Home screen.
Home.Js
export default class Home extends Component {
state = {
total: 1000
};
decreaseTotalHandlerNavigate = () => {
this.props.navigation.navigate("Decrease", { clickMe: this.changeTotal });
};
changeTotal = number => {
this.setState(prevState => ({ total: prevState.total - number }));
};
render() {
return (
<View>
<Text>{this.state.total}</Text>
<TouchableOpacity onPress={this.decreaseTotalHandlerNavigate}>
<Text>Decrease</Text>
</TouchableOpacity>
</View>
);
}
}
Decrease.js
export default class Decrease extends Component {
state = {
number: 0
};
render() {
return (
<View>
<TextInput
onChangeText={value => {
this.setState({
number: value
});
}}
/>
<TouchableOpacity onPress={this.props.navigation.getParam("clickMe", "changeTotal")}>
<Text>Add</Text>
</TouchableOpacity>
</View>
);
}
}

How to call a function in a child navigator's screen from a parent navigator's header icon

I'm using react navigation. My top level navigator is a StackNavigator. Within this StackNavigator I have a nested DrawerNavigator. I really want this implementation because I don't want the Navigation Drawer to overlap the header.
In the StackNavigator I have placed an icon (headerRight) in the header. I have a MapView as the first screen route in the nested DrawerNavigator. I want to zoom to the user's current location (which is held globally in a Context) when the icon is pressed.
I can't figure out how to do this. In order for it to work I would need a reference the MapView from the icon's onPress so I can zoom to a specific location. I don't want the DrawerNavigator to be my top-level navigator. What can I do? Here is the code for my navigators:
const DrawerNavigator = createDrawerNavigator({
"Search Locations": {
screen: (props) => (
<CurrentLocationProvider>
<BusinessLocationsProvider>
<SearchLocationsScreen {...props}/>
</BusinessLocationsProvider>
</CurrentLocationProvider>
),
},
"About": {
screen: AboutScreen,
},
"Favorites": {
screen: FavoritesScreen
},
"Profile": {
screen: ProfileScreen
},
"Issues": {
screen: ReportIssueScreen
}
}, {
contentComponent: props => <CustomDrawerComponent {...props} />,
});
const MainStackNavigator = createStackNavigator({
DrawerNavigator: {
screen: DrawerNavigator,
},
"Checkin": {
screen: CheckInScreen,
},
"Confirmation": {
screen: (props) => (
<CurrentLocationProvider>
<CheckInConfirmationScreen {...props}/>
</CurrentLocationProvider>
),
navigationOptions: {
header: null,
}
}
}, {
defaultNavigationOptions: ({navigation}) => configureNavigationOptions(navigation)
});
const LoadingNavigator = createSwitchNavigator({
"LoadingScreen": LoadingScreen,
MainStackNavigator: MainStackNavigator,
}, {
initialRouteName: "LoadingScreen",
});
export default createAppContainer(LoadingNavigator);
Here is the configuration I use for the defaultNavigationOptions in the StackNavigator:
export default (navigation) => {
const {state} = navigation;
let navOptions = {};
// navOptions.header = null;
navOptions.headerTitle = <Text style={styles.headerTitle}>Nail-Queue</Text>;
navOptions.headerStyle = {
backgroundColor: colors.primary
};
if (state.index === 0) {
navOptions.headerRight = (
<TouchableOpacity onPress={() => {
}}>
<MaterialIcons
name="my-location"
size={32}
color="#fff"
style={{paddingRight: 10}}
/>
</TouchableOpacity>
)
}
if (state.isDrawerOpen) {
navOptions.headerLeft = (
<>
<StatusBar barStyle='light-content'/>
<TouchableOpacity onPress={() => {
navigation.dispatch(DrawerActions.toggleDrawer())
}}>
<Ionicons name="ios-close" style={styles.menuClose} size={38} color={'#fff'}/>
</TouchableOpacity>
</>
)
} else {
navOptions.headerLeft = (
<>
<StatusBar barStyle='light-content'/>
<TouchableOpacity onPress={() => {
navigation.dispatch(DrawerActions.toggleDrawer())
}}>
<Ionicons name="ios-menu" style={styles.menuOpen} size={32} color={'#fff'}/>
</TouchableOpacity>
</>
)
}
return navOptions;
};
It shouldn't be this difficult to perform such a basic operation but I've been reading documentation for 2 days and getting nowhere.
Use a global variable so store map view reference value, and access that reference in you custom header component to zoom to your position.

React Navigation Back Button Doesnt do anything

Basically what the title says. I am trying to create a back button by passing the current page routeName into the navigation state and then on the detail page I retrieve the params and pass the routeName into the goBack button. But when I click the button it doesnt do anything. I checked to see if the routeName of the previous page was in the state but it still doesnt do anything.
List Screen
Here is my Button, I am passing an id from a reducer and the routeName of the currentscreen into the navigation params
renderList = () => {
const { lists } = this.props;
if(lists) {
return (lists.data.map(el => {
return (
<TouchableOpacity
key={el.id}
onPress={ () => this.props.navigation.navigate('PostItem', { nid: el.id, prevScreen: this.props.navigation.state.routeName})}>
<Text>Next Screen</Text>
</TouchableOpacity>
)}
))
}
}
ListDetail Screen
class listDetail extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.loadPost('article', this.props.navigation.state.params.nid + '?include=field_image')
}
render() {
const { goBack } = this.props.navigation;
return (
<View>
<Button title="Back" onPress={() => goBack(this.props.navigation.state.params.prevScreen)} />
{ this.renderPost() }
</View>
)
}
}
any suggestions?
The parameter in goBack refers to the key not the routeName.
From the reactnavigation goBack docs
Note -- a key is not the name of the route but the unique identifier you provided when navigating to the route. See navigation key.
So in your example I'd try to call navigate with a key parameter
For renderList onPress could be
onPress={ () => this.props.navigation.navigate({ routeName: 'PostItem', key: `PostItem_${el.id}`, params: { nid: el.id } })}>
Then in ListDetail
<Button title="Back" onPress={() => goBack(`PostItem_${this.props.navigation.state.params.nid}`)} />

React-native: How to get navigationOptions with Custom TabBarComponent

I'm new to react native and i'm trying to build a custom tab bar but i'm facing a problem when trying to display icons tab bar.
Here what i achieve so far.
Here my Custom TabBar component:
class TabBar extends Component {
renderItem = (route, index) => {
const {
navigation,
jumpToIndex,
} = this.props;
const isCapture = route.routeName === 'AddExpenses';
const focused = index === navigation.state.index;
const color = focused ? activeTintColor : inactiveTintColor;
if (isCapture === true) {
return (
<TouchableOpacity
key={route.key}
style={Styles.tab}
onPress={() => (navigation.navigate('AddExpensesModal'))}
>
<Ionicons
name={ioniconsByPlatform('add-circle')}
style={Styles.icon}
size={26}
/>
</TouchableOpacity>
);
}
return (
<TouchableWithoutFeedback
key={route.key}
style={Styles.tab}
onPress={() => (isCapture ? navigation.navigate('CaptureModal') : jumpToIndex(index))}
>
<View style={Styles.tab}>
<Text style={{ color }}>{route.routeName}</Text>
</View>
</TouchableWithoutFeedback>
);
}
render() {
const {
navigation,
} = this.props;
const {
routes,
} = navigation.state;
return (
<View style={Styles.tabBar}>
{routes && routes.map(this.renderItem)}
</View>
);
}
}
export default TabBar;
My Tab Navigator:
const MainTabNavigator = TabNavigator({
Summary: { screen: SummaryScreen },
AddExpenses: { screen: ExpensesScreen },
Expenses: { screen: ExpensesScreen },
}, {
tabBarComponent: TabBar,
});
export default MainTabNavigator;
And an example of a screen where i try to set my TabBarIcon:
const SummaryScreen = () => (
<View style={Styles.container}>
<Text>Summary</Text>
</View>
);
SummaryScreen.navigationOptions = {
title: 'Summary',
tabBarIcon: props => <TabBarIcon {...props} name="pulse" />,
};
export default SummaryScreen;
I want to be able to display my tab bar icons thanks to the navigationOptions property.
Do you have any idea how i can do this ?
If you feel TabNavigator is not powerful enough(which I think it's far from powerful), you could always customize a navigator view.
Here is my notes for customize a navigator view to replace TabNavigator:
export default class SectionTabView extends React.Component {
static propTypes = {
navigation: PropTypes.object
};
constructor(props) {
super(props);
}
render() {
const {router, navigation} = this.props;
const {routes, index} = navigation.state;
/**
* ActiveScreen is the current screen you see when you change you navigation state in tab bar
*/
const ActiveScreen = router.getComponentForState(navigation.state);
return (
<View style={Styles.section_container}>
<ActiveScreen
navigation={addNavigationHelpers({
...navigation,
state: routes[index],
})}
/>
<SectionTabBar navigation={navigation}/>
</View>
);
}
}
export default class SectionTabBar extends React.Component {
static propTypes = {
navigation: PropTypes.object
};
constructor(props) {
super(props);
}
getTabButtomGroupView() {
const {navigation} = this.props;
const {routes, index} = navigation.state;
let tabButtomGroupView = [];
routes.map((route) => {
let styles = [Styles.eventSection_tab];
const isClicked = routes[index].routeName === route.routeName;
if(isClicked){
styles.push(Styles.eventSection_tabClicked);
}
tabButtomGroupView.push(
<TouchableOpacity
onPress={() => {
/**
* when the routeName is equal to current routeName, we should stop navigate action
*/
if (routes[index].routeName === route.routeName) {
return;
}
navigation.navigate(route.routeName);
}}
style={styles}
key={route.routeName}>
<Text style={{color:'white'}}>{SectionRouteConfig[route.routeName].navigationOptions.title}</Text>
</TouchableOpacity>
)
});
return tabButtomGroupView;
}
render() {
return (
<View style={Styles.section_tabContainer}>
{this.getTabButtomGroupView()}
</View>
);
};
}
//SectionRouteConfig.js
export const sectionRouteConfig = {
XXX: {
screen: XXX, navigationOptions: {
title: XXX
}
},
XXX: {
screen: XXX, navigationOptions: {
title: XXX
}
}
};
export const SectionNavigator = createNavigator(TabRouter(sectionRouteConfig))(SectionTabView);
//Usage
render() {
const {dispatch, navigationState} = this.props;
return (
<SectionNavigator
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState
})
}
/>
)
}
by the way I also use redux.
If those codes are too much for you , you can check the official example here:https://github.com/react-community/react-navigation/blob/master/examples/NavigationPlayground/js/CustomTabs.js