how to display headers in react navigation with TabNavigation - react-native

I noticed that views in StackNavigation show the header title but if I set those same screens in a TabNavigation it doesn't show a header. It only shows a header if I wrap a StackNavigation either around each tab, or wrap the TabNavigation nested inside a StackNavigation.
Why don't screens in TabNavigation show a header - is that expected behavior? If so, is it better to have a StackNavigation in each tab, or one big StackNavigation around the TabNavigation?
// tabs navigation doesn't show a header title in each screen
const TabsNavigator = TabNavigator({
Home: {
screen:HomeScreen,
},
Profile: {
screen: ProfileScreen,
},
}, {
tabBarOptions: {
activeTintColor: '#e91e63',
},
navigationOptions: {
header: {
visible: true,
},
},
});
Header shows when I wrap it in a StackNavigator
default StackNavigator({
Home: { screen: TabsNavigator },
});
Or is it better to do it this way
export TabsNavigator = TabNavigator({
Home: {
screen:StackNavigator({
Home: { screen: HomeScreen },
}),
},
Profile: {
screen: StackNavigator({Profile: {screen: ProfileScreen}}),
},
}, {
tabBarOptions: {
activeTintColor: '#e91e63',
},
navigationOptions: {
header: {
visible: true,
},
},
});

Even if this is a fairly old question, I had myself this question a couple days ago, so I'll be adding my two cents about it hoping this will be useful for someone else in the future as well.
React Navigation is an amazing product with a high amount of customization, but that also turns out sometimes to be confusing to begin with, which also applies to some sections of the documentation. navigationOptions as of the current version states, is common for all screens but the "list of available navigation options depends on the navigator the screen is added to." https://reactnavigation.org/docs/tab-navigator.html#navigationoptions-used-by-tabnavigator hence the header option doesn't work because it's not available for TabNavigator itself.
Regarding your question on which approach is the best that depends on what do you want to accomplish with the navigation for your app itself. If you put your TabNavigator inside a StackNavigator the header component will be common for all of the tabs inside the TabNavigator, meaning the tab transition will take effect but the header won't move from its top position. If you on the other hand choose to nest a StackNavigator inside every tab, the header will render inside every tab, meaning the header will move along the tab transition animation.
I made a quick demo for you to see the difference, the code is also available if you wanna play with it.

I understand that this question already has an answer to it, but I would like to just show the way that I was able to make this work for myself using React Navigation > 5.x.x.
Juan's answer describes the best way to set this up, but also showing how it's done so that people can understand what the navigation looks like is also a bit of help for us visual learners. This was a little bit hard for me to understand at the time but hoping that it will help others in the future.
I find the best way to look at this is by breaking down the hierarchy of your apps routes. For me mine looked something like this.
LoginNavigation HomeNavigation
| |
LoginScreen RegisterScreen HomeStack ProfileStack
| |
HomesScreen ProfileScreen
My LoginNavigation stack contains two screens the Login screen and Register screen. In my app I don't need tab navigation set here so it's appropriate to just have these screens wrapped in a StackNavigator. On the other hand I have my HomeNavigation stack which contains a Home screen and a Profile screen. The only issue is that my screens are wrapped in a TabNavigator which does not produce a header.
To fix this we actually need to wrap our two screens (HomeScreen & ProfileScreen) in StackNavigators and then wrap everything with the TabNavigator. After realizing this, it actually makes much more sense to do it this way. Why? Well let's say that in my home screen I have some posts and I want the user to be able to see the post and all the comments that come with it. Well I wouldn't setup another Tab for this because that would just be silly. Instead we would add it to our HomeStack navigation instead like so.
HomeNavigation
|
HomeStack ProfileStack
| |
HomesScreen PostScreen ProfileScreen
This structure now seems apparent when looking at something like the ProfileStack where there could be multiple branches to this stack, such as Settings, Followers, Pictures...
Hopefully displaying the structure helps someone understand this as well as it helped me.
Below is my AppStackNavigator file to see the structure in code.
import React from 'react';
import { createStackNavigator } from '#react-navigation/stack';
import Login from '../screens/Login';
import Register from '../screens/Register';
import FirstProfileImage from '../screens/FirstProfileImage';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import Home from '../screens/Home';
import Profile from './../screens/Profile';
const LoginStack = createStackNavigator();
const HomeTabs = createBottomTabNavigator();
const HomeStack = createStackNavigator();
const ProfileStack = createStackNavigator();
const AppStack = createStackNavigator();
const LoginStackNavigator = () => (
<LoginStack.Navigator screenOptions={{headerStyle: {elevation: 0},cardStyle: {backgroundColor: '#ffffff'}}}>
<LoginStack.Screen name="Login" component={Login}/>
<LoginStack.Screen name="Sign Up" component={Register}/>
<LoginStack.Screen name="Profile Image" component={FirstProfileImage}/>
</LoginStack.Navigator>
)
const HomeStackNavigator = () => (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={Home} />
</HomeStack.Navigator>
)
const ProfileStackNavigator = () => (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Profile" component={Profile} />
</ProfileStack.Navigator>
)
const HomeTabNavigator = () => (
<HomeTabs.Navigator>
<HomeTabs.Screen name="HomeStack" component={HomeStackNavigator} />
<HomeTabs.Screen name="ProfileStack" component={ProfileStackNavigator} />
</HomeTabs.Navigator>
)
const AppStackNavigator = () => (
<AppStack.Navigator initialRouteName={'HomeScreen'} screenOptions={{headerStyle: {elevation: 0},cardStyle: {backgroundColor: '#ffffff'}}} headerMode='none'>
<AppStack.Screen name="LoginScreen" component={LoginStackNavigator}/>
<AppStack.Screen name="HomeScreen" component={HomeTabNavigator}/>
</AppStack.Navigator>
)
export default AppStackNavigator;
I do agree that this is a bit overkill and that we could eliminate the issue of having to keep track of each individual navigator, but it's what works for now until they add a feature for this. Hopefully this explanation helps a bit. Thanks for coming to my TED talk.

According to React-Navigation TabNavigator Docs there is no header navigationOption. Therefore, when you write the following code you are actually setting a nonexisting value thus what you are doing does not affect anything.
navigationOptions: {
header: { visible: true },
}
Sadly, you need a StackNavigator in this situation.

A general structure that I use is this, create a tab navigator and then render stacks for each tab. In this example I don't want a header on my LiveMap but I do on the other two tabs. The naming is up to you for whatever makes sense. Then inside each stack I render the screens that make sense.
const Tab = createBottomTabNavigator();
const TrainStack = createStackNavigator();
const MapStack = createStackNavigator();
const BusStack = createStackNavigator();
then each stack renders whatever screens I want for that subset
const MapNavigator = () => {
return (
<MapStack.Navigator
initialRouteName="LiveMap"
screenOptions={{headerShown: false}}>
<MapStack.Screen name="Live Map" component={LiveMap} />
<MapStack.Screen name="Show Routes" component={AllRoutes} />
<MapStack.Screen name="Select Routes" component={SelectRoutes} />
</MapStack.Navigator>
);
};
and the tab navigator will render my stacks for each tab.
const MainNavigator = () => (
<Tab.Navigator
tabBarOptions={{
activeTintColor: 'orange',
keyboardHidesTabBar: true,
}}
initialRouteName="Live Map">
<Tab.Screen
name="Buses"
component={BusNavigator}
options={{
tabBarLabel: 'Buses',
tabBarIcon: ({color, size}) => (
<BusIcon height={30} width={30} color={color} />
),
}}
/>
<Tab.Screen
name="Live Map"
component={MapNavigator}
options={{
tabBarLabel: 'Map',
tabBarIcon: ({color}) => (
<View
height={100}
width={100}
style={{
display: 'flex',
alignItems: 'center',
backgroundColor: '#fff',
paddingTop: 8,
borderRadius: 70,
}}>
<MapIcon height={50} width={50} color="orange" />
</View>
),
}}
/>
<Tab.Screen
name="Trains"
component={TrainNavigator}
options={{
tabBarLabel: 'Trains',
tabBarIcon: ({color, size}) => (
<TrainIcon height={30} width={30} color={color} />
),
}}
/>
</Tab.Navigator>

Related

React Navigation 6 (RN6) - Card stack within a modal

I have a question about a card stack inside a modal stack as illustrated in the attached image.
So, just to repeat what I wanted to do. I have a screen with the option presentation: 'modal' that opens the green modal.
Inside that green modal, I have a button that should invoke a navigation call that should show the blue screen with option presentation: 'card' and the ability to go back to the green screen.
I have done something similar with the react-native-navigation library from WIX but I have no idea if that can be done with react-navigation.
Any help is much appreciated.
Cheers
I found the solution with Nesting navigators as described here
Basically, I created a ModalStack and used this stack in Screen component as shown below.
import React from 'react'
import { createNativeStackNavigator } from '#react-navigation/native-stack'
import { TransitionPresets } from '#react-navigation/stack'
import HomeView from '../screens/HomeView'
import ModalView from '../screens/ModalView'
import CardView from '../screens/CardView'
const RootStack = createNativeStackNavigator()
const ModalStack = createNativeStackNavigator()
const ModalStackView = () => (
<ModalStack.Navigator
screenOptions={{
headerShown: true,
}}>
<ModalStack.Screen
name="modalCard1"
component={ModalView}
options={{
presentation: 'modal'
}}
/>
<ModalStack.Screen
name="modalCard2"
component={CardView}
options={{
presentation: 'card'
}}
/>
</ModalStack.Navigator>
)
const Stacks = () => (
<RootStack.Navigator
screenOptions={{
headerShown: false,
}}>
<RootStack.Screen name="home" component={HomeView} />
<RootStack.Screen
name="modal"
component={ModalStackView}
options={{
presentation: 'modal'
}}
/>
</RootStack.Navigator>
)
export default Stacks
Here the Snack with the full code

Dismiss the nested stack navigator

In my react-native project I am using libraries
"#react-navigation/native": "^5.8.10",
"#react-navigation/stack": "^5.12.8",
I have nested navigator, like this:
// root level I have a stack navigator where it contains two screens, `Home` and `Settings`.
const App = ()=> {
const rootStack = createStackNavigator();
return (
<NavigationContainer>
<rootStack.Navigator>
<rootStack.Screen name="Home" component={Home} />
<rootStack.Screen name="Settings" component={Settings} />
</rootStack.Navigator>
</NavigationContainer>
);
}
// The Settings screen is a nested stack navigator
const Settings = ()=> {
const settingsStack = createStackNavigator();
return (
<settingsStack.Navigator>
<settingsStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<settingsStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</settingsStack.Navigator>
);
}
As you can see, the Settings screen is actually another level (nested) stack navigator.
On SettingsOneScreen, there is a button navigates user to SettingsTwoScreen.
const SettingsOneScreen = ({navigation}) => {
...
return (
...
<Button onPress={()=>navigation.navigate("SettingsTwo")}/>
)
}
Now, on SettingsTwoScreen, I have a button, I would like to close the whole settings navigator stack when user tap on the button. That's dismiss the whole settings stack & show user the Home. How to achieve it?
const SettingsTwoScreen = ({navigation}) => {
...
return (
...
<Button onPress={/*dismiss the settings stack*/}/>
)
}
(Of course I can't use the navigation.goBack() which only navigate user back to the previous screen i.e. SettingOneScreen in this case.)
1-) use navigate.
//this will go back to Home and remove any screens after that.
navigation.navigate('Home')
docs say that.
In a stack navigator, calling navigate with a screen name will result in different behavior based on if the screen is already present or not. If the screen is already present in the stack's history, it'll go back to that screen and remove any screens after that. If the screen is not present, it'll push a new screen.
2-) use reset.
navigation.reset({
index: 0,
routes: [{ name: 'Home' }],
})}
see docs about reset
3-) use replace then goBack.
//from SettingsOne call replace instead navigate
//this will remove SettingsOne and push SettingsTwo
navigation.replace('SettingsTwo');
//then from SettingsTwo
//calling goBack will back to home because SettingsOne removed in last step
navigation.goBack();
4-) use one stack with pop.
import { StackActions } from '#react-navigation/native';
//merge two stacks in one
<NavigationContainer>
<rootStack.Navigator>
<rootStack.Screen name="Home" component={Home} />
<rootStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<rootStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</rootStack.Navigator>
</NavigationContainer>
//back 2 screen
navigation.dispatch(StackActions.pop(2));
see docs about pop
for methods 1, 2 you can try snack here.
I have faced the same problem before, this will help you more
<Button onPress={()=>navigation.navigate("Settings",{
screen: 'SettingsTwo',
params: { data: data }//put here the data that you want to send to SettingTow
)}
/>
//more explanation
goto = (data) => {
navigation.navigate('parent_stack', {
screen: 'screen_on_children_stack',
params: { data: data }
});
}
You can create a Switch navigator for the "root" app and create two stacks "home" and "setting".
const Root = ()=> (createAppContainer(createSwitchNavigator(
{
Home: Home,
Settings: Settings,
},
{
initialRouteName: 'Home',
}
)
// The Settings screen is a nested stack navigator
const Settings = ()=> {
const settingsStack = createStackNavigator();
return (
<settingsStack.Navigator>
<settingsStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<settingsStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</settingsStack.Navigator>
);
}
Then you can easily switch between stacks of Home and Settings
this.props.navigation.navigate('Settings');
https://reactnavigation.org/docs/upgrading-from-4.x/#dismiss
navigation.dangerouslyGetParent().pop();

How to navigate to other screen using the headerRight on the navigator code?

The title is very confusing, but the explanation that I will give will be more clear.
I am creating one StackNavigator for my app, and I am defining one icon to be displayed on the header of one of my screens like so:
const navigator= createStackNavigator(
{
Initial: {
screen: Posts,
navigationOptions: {
title: "All Posts",
headerRight: () => {
return (
<TouchableOpacity style={{ padding: 5 }}>
<Feather name={"plus"} size={30} />
</TouchableOpacity>
);
},
},
},
Details: Details,
Create: Create,
},
{
initialRouteName: "Initial",
}
);
const App = createAppContainer(navigator);
export default () => {
return (
<Provider>
<App />
</Provider>
);
};
The problem is that I want to navigate the user to the Create screen when the user presses the icon that I am rendering on the right side of the header, but I do not know how to have access to the navigation.navigate() function that the navigator generates for all the screens. I know that I can define the header on the Posts' screen file, so I have access to the navigation.navigate() function, but I want to know if there is some way to do it on the App.js file.
EDIT
Reading the documentation I saw that the way that I am was creating the navigator is not what the official documentation recommends. I learned to make it like this by one old course, using React Navigation 4.x, and now with React Navigation 6.x I perceived the difference in the creation and changed the way that I was doing it on my app. You can check the documentation for the problem that I was having here
You can prepare your navigation option this way by sending props
options={(props) => ({
headerRight: () => <TouchableOpacity
onPress={()=>props.navigation.navigate('Screen')} style={{ padding: 5 }}>
<Feather name={"plus"} size={30} />
</TouchableOpacity>
})}

Set Screen Title in ReactNative createBottomTabNavigator

I am trying to set the Screen Title of the Navigated Screen by using navigator. But it's not working/ changing and doesn't throw an exception either.
Scenario
I want to load some states from Redux and display nice Header Title in the Navigation Screen when the user clicks on the bottom Tab.
User clicks on second Tab in bottom navigation tab
Load state from Redux
Set Header Title
Expected
I want to set the following text to the Screen title
headerTitle set in Screen
Actual
I declared my BottomTabNavigator as following:
import FilterScreen from '../screens/FilterScreen';
import ItemsListScreen2 from '../screens/ItemsListScreen copy';
const BottomTab = createBottomTabNavigator();
const INITIAL_ROUTE_NAME = 'List';
export default function BottomTabNavigator({ navigation, route }) {
// this code works
navigation.setOptions({ headerTitle: 'header Set in Navigator' });
const [isFilterVisible, toggleFilter] = useState(false);
return (
<>
<BottomTab.Navigator
initialRouteName={INITIAL_ROUTE_NAME}
tabBarOptions={{
inactiveBackgroundColor: '#42a5f5',
inactiveTintColor: '#ffffff'
}}
>
<BottomTab.Screen
name="List"
component={ItemsListScreen}
options={{
title: 'List',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-calendar" iconType="ion" />,
}}
/>
<BottomTab.Screen
name="List2"
headerMode="screen"
component={ItemsListScreen2}
screen
options={{
title: 'title List2',
headerTitle: 'my header title',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-calendar" iconType="ion" />,
}}
/>
....
</BottomTab.Navigator>
</>
);
}
In the above component, I could set the Screen Title by using navigation.setOptions and it works.
So, I tried to do the same operation to set the headerTitle in List2 Screen. But it doesn't have any effect to the screen header. It only changes the label of the tab to "this is a title".
import { StyleSheet, View, Text } from 'react-native';
const ItemsListScreen2 = ({ navigation }) => {
navigation.setOptions({
headerTitle: `headerTitle set in Screen`, // <-- no effect at the screen level
title: "this is a title"
});
return (
<View>
<Text>Screen 2</Text>
</View>
);
}
I tried to do console.log to navigation object passed to the component and I can see it here:
Could you please help me to sort out this issue and suggest me how I could set the title from the screen?
Do I need to create a new function (Eg. getHeaderTitle), load the Redux state and set the title due to the route within BottomTabNavigation component? I want to keep that component clean and I want to load the Redux States only when the user navigates to the screen. I'd like to keep the screen specific codes in its own related screen.

React Navigation (V2 / V3): How to access navigation.state.index in navigationOptions on screen

I'm building a React Native app and for my navigation I use React Navigation (V3). For my UI elements I use React Native Elements.
I have a screen which I reuse in multiple navigators. Sometimes it is at the root of a stack, and sometimes it's pushed on with a stack. I want to programmatically add a headerLeft element to this screen navigationOptions based on its position in the stack, because I either want the default back button to show, or a burger menu icon to show, if the screen is at the root of the stack (because the stack is nested in a drawer).
So I tried the following:
export class ScannerScreen extends Component {
static navigationOptions = ({ navigation }) => ({
headerLeft:
navigation.state.index > 0 ? (
undefined
) : (
<Icon type="ionicon" name="ios-menu" onPress={navigation.toggleDrawer} />
),
title: strings.scannerTitle
});
// ...
The problem is this doesn't work as navigation.state.index is undefined here. How would one do this with React navigation?
Edit:
As per request I added a screenshot of console.log(navigation) (via <Icon />s onPress.)
I found a hacky solution, which is okay but I also dislike it a bit:
const stackConfig: StackNavigatorConfig = {
cardStyle: styles.card,
navigationOptions: {
headerBackTitleStyle: styles.headerBackTitle,
headerStyle: styles.header,
headerTintColor: "black"
}
};
const HomeStack = createStackNavigator(
{ HomeScreen, RestaurantDetailScreen, MenuScreen, ScannerScreen },
{
...stackConfig,
initialRouteName: "HomeScreen",
navigationOptions: ({ navigation }) => ({
headerLeft:
parseFloat(navigation.state.key.slice(-1)) > 1 ? (
undefined
) : (
<Icon
containerStyle={styles.leftIcon}
type="ionicon"
name={getIconName("menu")}
onPress={navigation.toggleDrawer}
/>
),
...stackConfig.navigationOptions
})
}
);
This way it automatically sets it for the whole stack. You can also do this in the respective screen individual navigation options. But this only works, since the automatically assigned key for the screen contains it's index.