double header stack navigation react-native - reactjs

I am using a StackNavigator in react native app.
The problem is that in my app, it creates two headers ...
I would like to keep the upper one to go back from the screen. Is it possible without recreate the back button manually ?
Screen:
class CommandsList extends React.Component {
constructor(props){
super(props);
}
addCommand = () => {
this.props.navigation.navigate("CreateCommand");
}
render() {
return (
<SafeAreaView style={{flex:1}}>
<MyList itemsUrl="http://localhost:9000/commands"/>
<Button title="Ajouter" onPress={this.addCommand}></Button>
</SafeAreaView>
);
}
}
export default StackNavigator({
CommandsList : {
screen : CommandsList,
},
});
EDIT :
App.js
const navigationOptions = ({ navigation }) => ({headerLeft: <Icon name {'chevron-left'} onPress={ () => { navigation.goBack() }} />})
const RootStack = StackNavigator(
{
CommandsList: {
screen: CommandsList,
},
CreateCommand: {
screen: CreateCommand,
},
ListFournisseurs: {
screen: ListFournisseurs,
},
ListAffaires: {
screen: ListAffaires,
}
},
{
initialRouteName: 'CommandsList',
headerMode:'none',
navigationOptions:{navigationOptions}
}
);

According to the docs, if you want to disable the header of the StackNavigator, you can apply the config at your StackNavigatorConfig as headerMode: 'none'. It is back propagated from Child to Parent, if Parent is none then Child will also not be rendered.
Therefore for a single header, in your case you should do
export default StackNavigator({
CommandsList : {
screen : CommandsList,
},
}, {
headerMode: 'none'
});
For the back button in the Parent Stack, you can create a component as
const navigationOptions = ({ navigation }) => ({
headerLeft: <Icon name={'arrow-left'} //<== Vector Icon Here
onPress={ () => { navigation.goBack() }} />
const RootStack = StackNavigator(
RouteConfigs,
//... StackNavigatorConfigs,
navigationOptions
)

Related

How can I get the default HeaderTitle which was set in Main tab Navigator?

I want to get the default HeaderTitle and update it as per my component.
Header Title is added in MainTabNavigator as
const navOptions = ({ navigation }) => {
return {
headerTitle: (
<Image source={require("../assets/images/common/logo.png")}/>
),
};
};
const HomeStack = createStackNavigator(
{
Home: {
screen: Dashboard
}
},
{
initialRouteName: "Home",
defaultNavigationOptions: navOptions
},
);
I want to toggle between headerTitle on some events please suggest me the solution as I want to keep the headerTitle only on one location and toggle between it as per required.
static navigationOptions = ({ navigation }) => ({
header: QueryTocheck ? keepDefaultOldHeaderHere:updateHeaderHere
});

Nested tab navigators don't work inside drawer navigator

I'm trying to embed tab navigators within a drawer navigator, but the drawer navigator sometimes stops working. Code: https://github.com/myplaceonline/testreactexpo/tree/drawernestedtabs
To reproduce the problem:
Click the Screen2 tab at the bottom
Open the drawer
Click Home
Open the drawer
Click Screen2
Nothing happens
Is there a better way to do this and keep the drawer and bottom tabs synchronized and avoid the issue where the drawer stops working?
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
createAppContainer,
createBottomTabNavigator,
createDrawerNavigator,
createStackNavigator,
NavigationActions,
DrawerActions,
} from 'react-navigation';
import { Ionicons } from '#expo/vector-icons';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#ffffff",
alignItems: "center",
justifyContent: "center",
},
});
class HomeScreen extends React.Component {
static navigationOptions = {
title: "Home",
};
render() {
return (
<View style={styles.container}>
<Text>Home</Text>
</View>
);
}
}
class Screen2Screen extends React.Component {
static navigationOptions = {
title: "Screen2",
};
render() {
return (
<View style={styles.container}>
<Text>Screen2</Text>
</View>
);
}
}
const AppScreensTabs = {
Home: HomeScreen,
Screen2: Screen2Screen,
};
const AppScreensTabOptions = {
tabBarOptions: {
showLabel: true,
},
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
const { routeName } = navigation.state;
let iconName;
// https://expo.github.io/vector-icons/
if (routeName === "Home") {
iconName = "md-home";
} else if (routeName === "Screen2") {
iconName = "md-beer";
}
return <Ionicons name={iconName} size={25} color={tintColor} />;
},
}),
};
const AppScreens = {
TabHome: createBottomTabNavigator(
AppScreensTabs,
Object.assign(
{ initialRouteName: "Home" },
AppScreensTabOptions,
)
),
TabScreen2: createBottomTabNavigator(
AppScreensTabs,
Object.assign(
{ initialRouteName: "Screen2" },
AppScreensTabOptions,
)
),
};
const AppScreensStackNavigationOptions = {
defaultNavigationOptions: ({ navigation }) => ({
headerLeft: <Ionicons name="md-menu" size={25} onPress={ () => navigation.openDrawer() } style={{ marginLeft: 15 }} />
})
};
const AppDrawer = createAppContainer(createDrawerNavigator({
DrawerHome: {
screen: createStackNavigator(
AppScreens,
Object.assign(
{ initialRouteName: "TabHome" },
AppScreensStackNavigationOptions,
)
),
navigationOptions: {
drawerLabel: "Home",
}
},
DrawerScreen2: {
screen: createStackNavigator(
AppScreens,
Object.assign(
{ initialRouteName: "TabScreen2" },
AppScreensStackNavigationOptions,
)
),
navigationOptions: {
drawerLabel: "Screen2",
}
},
}));
export default class App extends React.Component {
render() {
return (
<AppDrawer />
);
}
}
<div data-snack-id="#git/github.com/myplaceonline/testreactexpo#drawernestedtabs" data-snack-platform="ios" data-snack-preview="true" data-snack-theme="light" style="overflow:hidden;background:#fafafa;border:1px solid rgba(0,0,0,.08);border-radius:4px;height:505px;width:100%"></div>
<script async src="https://snack.expo.io/embed.js"></script>
It seems you're trying to keep drawer state and tab state in sync, but I guess that from a UX perspective, it might make more sense to treat them as separate navigation containers, each with their own navigation hierarchy. Keeping them in sync is not straight-forward using react-navigation and is not a pattern I think people will be familiar with when navigation through your app.

react navigation v3 set screenProps through AppContainer

I need to pass theme settings to navigation options of each screen. Settings are stored in redux. In old version I would write something like that:
const AppNavigator = StackNavigator({
Home: { screen: MainList },
Settings: { screen: Settings },
NewEvent: { screen: NewEvent },
Contacts: { screen: Contacts },
Map: { screen: Map},
})
And in render function...
<AppNavigator
screenProps={{
settings: this.props.settings,
headerTintColor: '#ffffff'
}}
/>
...where props.settings is a part of redux state. Which is used inside navigationOptions
static navigationOptions = ({ navigation, screenProps }) => {
let settings = screenProps.settings;
let theme = settings? settings.theme: null;
return {
title: strings.account,
headerTintColor: screenProps.headerTintColor,
headerStyle: {backgroundColor: theme? theme.def.primaryColor: null}
};
};
But how should I do this now when I must wrap my Navigators with createAppContainer? Using navigation.setParams makes a visible delay in emulator so its not a proper solution for me... Keep in mind theme can be changed at any time!
Well) Turnsout you can achive that by creating a custom navigator like this:
class NavWrapper extends Component {
static router = AppNavigator.router;
render() {
const { navigation } = this.props;
return <AppNavigator
navigation={navigation}
screenProps={{
settings: this.props.settings,
headerTintColor: '#ffffff'
}}
/>;
}
}
const AppNavWrapper = connect(state => ({settings: state.settings}))(NavWrapper);
const AppContainer = createAppContainer(AppNavWrapper);
And then in your root component
render() {
return (
<AppContainer/>
);
}
Hope that helps :)

Nested TabNavigator inside StackNavigator: controlling the header

I have a setup much like this:
let Tabs = createBottomTabNavigator({
screen1: Screen1,
screen2: Screen2
})
let Stack = createStackNavigator({
tabs: Tabs
otherScreen: OtherScreen
})
The stack navigator has a header, and that's fine. What I want is to get different header icons depending on what tab I'm currently on.
I'm using the following versions:
"react": "16.3.1",
"react-native": "~0.55.2",
"react-navigation": "^2.2.5"
I've considered switching up my setup so that each tab screen has its own StackNavigator but I like having the sliding animation when switching tabs and I don't want the header icons to slide along. The header bar should remain static but simply show different icons depending on the current tab.
You can use like this below, https://reactnavigation.org/docs/en/stack-navigator.html
//Screen1 Stack.
const Screen1 = createStackNavigator ({
Home: {
screen: Home,
navigationOptions: {
header: null //Need to set header as null.
}
}
});
//Screen2 Stack
const Screen2 = createStackNavigator ({
Profile: {
screen: Profile,
navigationOptions: {
header: null //Need to set header as null.
}
}
});
let Tabs = createMaterialTopTabNavigator({
Screen1:{
screen: Screen1 //Calling Screen1 Stack.
},
Screen2:{
screen: Screen2 //Calling Screen2 Stack.
}
},{ tabBarPosition: 'bottom' }) //this will set the TabBar at Bottom of your screen.
let Stack = createStackNavigator({
tabs:{
screen: Tabs, //You can add the NavigationOption here with navigation as parameter using destructuring.
navigationOptions: ({navigation})=>{
//title: (navigation.state.routes[navigation.state.index])["routeName"]
//this will fetch the routeName of Tabs in TabNavigation. If you set the routename of the TabNavigation as your Header.
//use the following title property,this will fetch the current stack's routeName which will be set as your header in the TabBar.
//title: (navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName
//you can use switch case,on matching the route name you can set title of the header that you want and also header left and right icons similarly.
switch ((navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName) {
case "Screen1":
return {
title: "Home",
headerLeft: (<Button
onPress={()=> alert("hi")}
title="Back"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/> ),
headerRight: <Button title= "Right"/>
}
case "Screen2":
return {
title: "Profile",
headerLeft: (<Button
onPress={()=> alert("hi")}
title="Back"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/> ),
headerRight: <Button title= "Right"/>
}
default:
return { title: (navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName }
}
}
},
otherScreen:{
screen: OtherScreen
}
})
//navigationOptions
navigationOptions: ({navigation})=>{
//title: (navigation.state.routes[navigation.state.index])["routeName"]
//this will fetch the routeName of Tabs in TabNavigation. If you set the routename of the TabNavigation as your Header.
//use the following title property,this will fetch the current stack's routeName which will be set as your header in the TabBar.
//title: (navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName
switch ((navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName) {
case "Screen1":
return {
title: "Home",
headerLeft: (<Button
onPress={()=> alert("hi")} //Here you can able to set the back behaviour.
title="Back"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/> ),
headerRight: <Button title= "Right"/>
}
case "Screen2":
return {
title: "Profile",
headerLeft: (<Button
onPress={()=> alert("hi")}
title="Back"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/> ),
headerRight: <Button title= "Right"/>
}
default:
return { title: (navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName }
}
}
//alert(navigation.state)
{
"routes":[
{
"key":"Screen1",
"routeName":"Screen1",
"routes":[
{
"key":"Home",
"routeName":"Home",
}
],
"index":0,
"isTransitioning":false,
"key":"id-1530276062643-0"
},
{
"key":"Screen2",
"routeName":"Screen2",
"routes":[
{
"key":"Profile",
"routeName":"Profile",
}
],
"index":0,
"isTransitioning":false,
"key":"id-1530276062643-0"
}
],
"index":0,
"isTransitioning":false,
"routeName":"tabs",
"key":"id-1530276062643-0"
}
//(navigation.state.routes[navigation.state.index])["routeName"]
//(navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName
this will give the current route name of the tab inside StackNavigation.
The above code will set the title in root stack header where the TabBar resides as first route,so we are setting the header as null for the individual stack in TabBar. By using this way will provide Animation while switching the screens in TabBar since header will remain static.
you can find the working copy here https://www.dropbox.com/s/jca6ssn9zkzh9kn/Archive.zip?dl=0
Download this and Execute the following.
npm install //to get dependencies
react-native upgrade //to get android and ios folder
react-native link //to link dependencies and libraries
react-native run-ios (or) react-native run-android
you can use the above, Let me know if any.
You can achieve the desired behavior with the current navigation stack configuration of yours. You might need to change couple of things and combine couple of properties but it is fairly simple when you get yor head around it.
I try to explain it with a small example.
Consider having below navigators;
const Tabs = createBottomTabNavigator({
screen1: Tab1,
screen2: Tab2
})
const Stack = createStackNavigator({
tabs: {
screen: TabsPage,
navigationOptions: ({navigation}) => {
return { title: (navigation.state.params && navigation.state.params.title ? navigation.state.params.title : 'No Title' ) }
}
},
otherScreen: Page
})
As you can see I am setting the title parameter from the navigation state. To be ale to set the param for that navigator, we are going to get help from screenProps property;
class TabsPage extends Component {
onTabsChange = (title) => {
this.props.navigation.setParams({ title })
}
render() {
return(<Tabs screenProps={{ onTabsChange: this.onTabsChange }} />)
}
}
I created a wrapper component for tab navigator and passed down a function which is setting the title parameter.
For the last part we need to know how and when to use that function we passed down. For that we are going to use addListener navigation prop
class Tab1 extends React.Component {
setTitle = () => {
this.props.screenProps.onTabsChange('Title from Tab 1')
}
componentDidMount() {
this.props.navigation.addListener('willFocus', this.setTitle)
}
render() {
return <View><Text>{'Tab1'}</Text></View>
}
}
When our tab focused the passed function will run and then set the title for that tab. You can use this process for setting different buttons or icons for the header. You can find the working snack here.
const RootStack = createStackNavigator(
{
Home: {screen: HomeScreen},
FilterScreen: createMaterialTopTabNavigator({
Tab1: {screen:Tab1Screen},
Tab2: {screen: Tab2Screen},
}),
},
{
mode: 'modal',
headerMode: 'none',
}
);
render() {
return <RootStack/>;
}
In AppNavigation.js //or where you have defined your routes.
let Tabs = createBottomTabNavigator({
screen1: Screen1,
screen2: Screen2
})
let Stack = createStackNavigator({
tabs: Tabs
otherScreen: OtherScreen
},{
headerMode:"float", //Render a single header that stays at the top and animates as screens are changed. This is a common pattern on iOS.
headerTransitionPreset:"fade-in-place" //this will give a slight transition when header icon change.
}
)
In Screen1.js
class Screen1 extends Component {
static navigationOptions = ({ navigation }) => {
return {
...
headerLeft: <HeaderLeftImage navigation={navigation} image={"Image_For_Screen1"}/>,
...
}
}
...
}
NOTE: You can add title, headersStyle,headerRight in same way read this link header configuration for more detail
In Screen2.js do similar to Screen1
class Screen2 extends Component {
static navigationOptions = ({ navigation }) => {
return {
...
headerLeft: <HeaderLeftImage navigation={navigation} image={"Image_For_Screen2"}/>,
...
}
}
...
}
Header.js
export const HeaderLeftImage = (props) => (
<View style={{
'add styles'
}}>
<TouchableOpacity onPress={() => {"Add action here" }}>
<Image source={props.image} resizeMethod='resize' style={{ height: 30, width: 90, resizeMode: 'contain' }} />
</TouchableOpacity>
</View>
)
Hope this helps, if you have any doubt regarding the code feel free to ask.
If you are using react navigation <2 i.e ~1.5.* You can set it like this.
const Tabs = TabNavigator({
Tab1:{
screen: Tab1,
navigationOptions: ({navigation}) => {
return { title: "Tab 1 Heading", tabBarLabel:"Tab 1 "}
},
}
Tab2:{
screen: Tab2
navigationOptions: ({navigation}) => {
return { title: "Tab 2 Heading", tabBarLabel:"Tab 2 "}
}
}
})
const Stack = StackNavigator({
tabs: {
screen: Tabs
navigationOptions: ({navigation}) => {
return { title: "Stack"}
}
},
otherScreen: Page
})
I'm not sure why did they remove this feature, when you try the same it won't work on the latest react-navigation. Now title object key is used for fallback it seems.
This may be helpful some users. If you wish you can try downgrading also.
I have the Upgraded the React Navigation for my project I think this way will be useful to someone
const Tabs = TabNavigator({
Tab1:{
screen: Tab1,
navigationOptions: ({navigation}) => {
return { tabBarLabel:"Tab 1 "}
}},
Tab2:{
screen: Tab2
navigationOptions: ({navigation}) => {
return { tabBarLabel:"Tab 2 "}
}}
});
Tabs.navigationOptions = ({navigation})=>{
const { routeName } = navigation.state.routes[navigation.state.index]; //This gives current route
switch(routeName){
case "Tab1":
headerTitle="Tab 1";
break;
case "Tab1":
headerTitle="Tab 1";
break;
}
return {
headerTitle: headerTitle
}
}
You can simply use property headerShown: false. I have created a working example of react-navigation version: 5.x.x, nesting different navigators: stack, drawer & bottom-tabs.
Here's the link to github:
https://github.com/mvpbuddy/react-native-nested-navigators/
For a more detailed overview, you can checkout my blog:
https://mvpbuddy.io/blog/detail/how-to-build-an-app-with-nested-stack-drawer-bottom-tab-navigators

States in TabStackNavigator?

It seems like TabNavigator doesn't have it's own state. Is there any way to use state or props?
I want to show the number of unread notification on the Notification TabIcon.
export default TabNavigator(
{
...
Noti: {
screen: NotificationStackNavigator,
},
...
},
{
navigationOptions: ({ navigation }) => ({
header: null,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
let iconName;
switch (routeName) {
...
case 'Noti':
iconName = 'ios-notifications';
break;
...
}
...
if(iconName == 'ios-notifications') {
return (
<IconBadge
MainElement={
<Ionicons
name={iconName}
size={30}
style={{ marginBottom: -3 }}
color={focused ? Colors.tabIconSelected : Colors.tabIconDefault}/>
}
BadgeElement={
<Text style={{color:'#FFFFFF'}}>
{{this.state.notifications}} // my goal
</Text>
}
IconBadgeStyle={{
backgroundColor: '#f64e59',
position: 'absolute',
right:-10,
top:0,
}}
/>
);
}
...
Please let me know if anything is unclear. Thanks in advance
UPDATE I'm planning to refactor my TabNavigator. Is this what you're trying to say?
export default TabNavigator(
to
const MainTabNavigator = TabNavigator(
class MainTabNavigator extends Component {
state = {notification}
export default connect(...)(MainTabNavigator);
UPDATE 2 MainTabNavigator's Top Level component is another TabNavigator. Is there any other solution?
const RootTabNavigator = TabNavigator ({
Auth: {
screen: AuthStackNavigator,
},
Welcome: {
screen: WelcomeScreen,
},
Main: {
screen: MainTabNavigator,
},
You can pass additional custom props via screenprops
const TabNav = TabNavigator({
// config
});
<TabNav
screenProps={/* this prop will get passed to the screen components as this.props.screenProps */}
/>
The full documentation is here

Resources