Hide Custom Tab Bar On A Specific Screen In React Navigation 6 - react-native

I am writing a react native app with react-navigation 6 that has a custom tab navigation, inside of which I have multiple stacks for each tab.
Custom Bottom Tab with Stacks as Tabs:
<Tab.Navigator tabBar={props => <CustomTabBar {...props} />}>
<Tab.Screen
name={ROUTES.SPEAKER_STACK}
component={SpeakerNavigator}
options={{
title: 'Speakers',
tabBarLabel: 'Speakers',
tabBarIcon: { activeIcon: 'mic-sharp', inActiveIcon: 'mic-outline' },
}}
/>
</Tab.Navigator>
Speaker Navigator:
<Stack.Navigator initialRouteName={ROUTES.SPEAKERS}>
<Stack.Screen name={ROUTES.SPEAKERS} component={Speakers} />
<Stack.Screen name={ROUTES.SEND_MESSAGE} component={SendMessage} />
</Stack.Navigator>
CustomTab:
const CustomTabBar = ({ state, descriptors, navigation }) => {
return (
<View style={styles.tabBarContainer} shadow="3">
<View style={styles.slidingTabContainer}>
<Animated.View style={[styles.slidingTab, animatedTabStyles]} />
</View>
{state.routes.map((route, index) => {
return (
<Pressable
key={index}
style={{ flex: 1, alignItems: 'center' }}>
<TabIcon
tabIcon={tabBarIcon}
/>
</Pressable>
);
})}
</View>
);
}
Problem:
I want to hide the <CustomTab /> on the Send Message screen since it is a chat screen.
Already Tried:
<Tab.Screen
name={ROUTES.SPEAKER_STACK}
component={SpeakerNavigator}
options={({ route }) => {
if (getFocusedRouteNameFromRoute(route) === 'Send Message') {
return {
tabBarVisible: false,
tabBarStyle: { display: 'none' },
};
}
}}
/>
I assume they don't work since my TabBar is a custom component.
If I can somehow get getFocusedRouteNameFromRoute(route) === 'Send Message' inside the CustomTab component, I will be able to hide it directly by setting tabBarContainer (View wrapper of my custom tab bar) to display: 'none'
I have also tried the same technique from inside the screen. Same result.
react-navigation suggests to change the app structure by placing TabNavigator inside StackNavigator. But my app doesn't allow that structure.

import { View, Text, TouchableOpacity, SafeAreaView } from "react-native";
import type { BottomTabBarProps as ReactNavigationBottomTabBarProps } from "#react-navigation/bottom-tabs";
import { hp } from "#src/components/responsive";
import { CallOutlineIcon, ChatOutlineIcon } from "#src/components/icons";
type BottomTabBarProps = ReactNavigationBottomTabBarProps;
export default function TabBar({
state,
descriptors,
navigation,
}: BottomTabBarProps) {
const focusedOptions = descriptors[state.routes[state.index].key].options;
const { display } = focusedOptions?.tabBarStyle as {
display: "none" | "flex";
};
// ! HIDE BOTTOM TAB SPECIFICALLY FOR CHAT SCREEN
if (display === "none") {
return null;
}
// rest of the code
return (
<SafeAreaView>
</SafeAreaView>
);
}

Related

React-Native Header Title and Tab Navigator

I’ve a problem I cannot find an answer to, even though I’ve found similar problems with answers.
Basically, I pull a name (and other info) from a database. I then navigate to a screen (Screen A) in a tab navigator. I place the name into the header title of that screen (needed a stack navigator to do that I discovered from this site). I then have a second tab (Screen B) I can navigate to and want that same name placed in the header title there.
While on Screen B I also need to change the name and have that placed back into the header title of both Screen A and Screen B.
How do I do this? I have example code below that hopefully explains in more detail.
App.js
import React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { HomeScreen, ScreenA, ScreenB } from './ui/screens.js';
const AStack = createStackNavigator();
function ScreenAStack(headerProps) {
return (
<AStack.Navigator>
<AStack.Screen name="Screen A" component={ScreenA} />
</AStack.Navigator>
);
}
const BStack = createStackNavigator();
function ScreenBStack(headerProps) {
return (
<BStack.Navigator>
<BStack.Screen name="Screen B" component={ScreenB} />
</BStack.Navigator>
);
}
const BottomTab = createBottomTabNavigator();
function Tabs() {
return (
<BottomTab.Navigator
screenOptions={{ headerShown: false }}
>
<BottomTab.Screen name="Screen A Stack" options={{ title: "Screen A" }}>
{(props) => (
<ScreenAStack {...props} />
)}
</BottomTab.Screen>
<BottomTab.Screen name="Screen B Stack" options={{ title: "Screen B" }}>
{(props) => (
<ScreenBStack {...props} />
)}
</BottomTab.Screen>
</BottomTab.Navigator>
);
}
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>{
<Stack.Navigator>
<>
<Stack.Screen name="Home Screen" options={() => ({ headerShown: true })} component={HomeScreen} />
<Stack.Screen name="Screen A Tabs" options={() => ({ headerShown: false })} component={Tabs} />
</>
</Stack.Navigator>
}</NavigationContainer>
);
}
export default App;
screens.js
import React, { useEffect } from 'react';
import { View, Text, TextInput, StyleSheet, Button, } from 'react-native';
export function HomeScreen({ navigation }) {
// Get name from server using api and send to Screen A.
navList = (item) => {
navigation.navigate("Screen A Tabs", {
screen: 'Screen A Stack',
params: {
screen: 'Screen A',
params: { item },
},
});
}
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen.</Text>
<Button title="Go to Tab Screen" onPress={() => navList({name: "Name here!"})} />
</View>
);
}
export function ScreenA({ route, navigation }) {
// Place name into header title.
useEffect(() => {
navigation.setOptions({
title: route.params.item.name,
});
}, [route]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Screen A</Text>
</View>
);
}
export function ScreenB({ navigation }) {
// Update header title with new name and update name on server using api.
const [newName, setNewName] = React.useState("");
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Screen B</Text>
<TextInput
value={newName}
onChangeText={newName => setNewName(newName)}
placeholder="Type new name here."
/>
<Button title="Change Name" onPress={() => console.log("Change name to " + newName)} />
</View>
);
}
I will give a short answer. your do not need to create a separate stacks for a single screen. here you can create a simple bottom tab navigator. just a sample.
<Tab.Navigator
initialRouteName="SearchScreen"
backBehavior="initialRoute"
screenOptions={{headerShown: false}}
>
<Tab.Screen
name="SearchScreen"
component={SearchScreen}
options={{
headerShown: false,
tabBarLabel: 'Search',
tabBarIcon: 'search',
}}
/>
<Tab.Screen
name="DialerScreen"
component={DialerScreen}
options={{
headerShown: false,
tabBarLabel: 'Dialer',
tabBarIcon: 'dialer',
}}
/>
</Tab.Navigator>
after this create a simple stack and add this bottom or top tab navigator like this
<Stack.Screen
name="Home"
component={TopTabNavigator}
options={{headerShown:true}}
/>
you can change title like this
onPress={() => {
if (item.is_analog) {
navigation.setOptions({title: 'Analog Speedo Meter'});
} else {
navigation.setOptions({title: 'Digital Speedo Meter'});
}
}}
cheers

Having two tab navigation bars in React Navigation

I want to create an app, that has both a fixed bottom and top tab navigation bar.
See image:
After I finished the bottom navigation bar I tried the following in my App.js file:
return(
<NavigationContainer>
<Tab.Navigator> //top navbar
<Tab.Screen />
...
</Tab.Navigator>
<Tab.Navigator> //bottom navbar
<Tab.Screen />
...
</Tab.Navigator>
</NavigationContainer>
)
However, I get the error, that another navigator is already registered in this container and that I should not have multiple navigators under a single NavigationContainer.
I found multiple guides about nesting tab and stack navigators, but how do I nest multiple tab navigators, that both update the central screen?
AFAIK that is not possible without writing a custom navigator. Navigators need to be nested and need to have separate routes, so one tab navigator would need to be nested inside (as a tab of) the other.
Writing a custom navigator is something you definitely could consider. Here is a snack that modifies the example from react-navigation documentation:
https://snack.expo.io/#mlisik/thoughtful-stroopwafels
In the snack, the first two tabs are displayed on top, and remaining on the bottom. You would need to further modify them to match the appearance you are after with some custom options, perhaps reusing internal components from react-navigation.
It is by no means a complete solution, but should give you an idea of what is possible.
For completeness, I include the code here:
// App.js
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNavigator } from './Navigator';
const Nav = createNavigator()
const Screen1 = () => <View style={{flex: 1, backgroundColor: 'red'}} />
const Screen2 = () => <View style={{flex: 1, backgroundColor: 'green'}} />
const Screen3 = () => <View style={{flex: 1, backgroundColor: 'yellow'}} />
const Screen4 = () => <View style={{flex: 1, backgroundColor: 'brown'}} />
export default function App() {
return (
<NavigationContainer>
<Nav.Navigator>
<Nav.Screen name="Tab 1" component={Screen1} />
<Nav.Screen name="Tab 2" component={Screen2} />
<Nav.Screen name="Tab 3" component={Screen3} />
<Nav.Screen name="Tab 4" component={Screen4} />
</Nav.Navigator>
</NavigationContainer>
)
}
// Navigator.js
// this is only slightly modified from https://reactnavigation.org/docs/custom-navigators#usenavigationbuilder
import * as React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import {
NavigationHelpersContext,
useNavigationBuilder,
createNavigatorFactory,
TabRouter,
TabActions,
} from '#react-navigation/native';
function TabButton({ route, descriptors, navigation, state }) {
return (
<TouchableOpacity
key={route.key}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
}}
style={{ flex: 1 }}
>
<Text>{descriptors[route.key].options.title || route.name}</Text>
</TouchableOpacity>
)
}
function Navigator({
initialRouteName,
children,
screenOptions,
tabBarStyle,
contentStyle,
}) {
const { state, navigation, descriptors } = useNavigationBuilder(TabRouter, {
children,
screenOptions,
initialRouteName,
});
const renderTab = (route) => (
<TabButton
route={route}
descriptors={descriptors}
state={state}
navigation={navigation}
/>
)
return (
<NavigationHelpersContext.Provider value={navigation}>
<View style={[{ flexDirection: 'row' }, tabBarStyle]}>
{state.routes.slice(0, 2).map(renderTab)}
</View>
<View style={[{ flex: 1 }, contentStyle]}>
{descriptors[state.routes[state.index].key].render()}
</View>
<View style={[{ flexDirection: 'row' }, tabBarStyle]}>
{state.routes.slice(2).map(renderTab)}
</View>
</NavigationHelpersContext.Provider>
);
}
export const createNavigator = createNavigatorFactory(Navigator);

React Navigation Nested No Route Params v5

I can't seem to get any route params in my nested navigator. The params are present in the parent navigator, but they are not reachable in the child navigator.
So the child navigator does render the correct screen, but it does not have any params in the route (namely a category or product id).
It feels like I am misusing some syntax, but I can't quite figure out which one. Here is the stack of code, edited down a bit to make it easier to read.
Snack on Expo
Thank you.
Note: These are separate files so the includes are off.
import * as React from 'react';
import { Text, View, StyleSheet, SafeAreaView } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { NavigationContainer } from '#react-navigation/native'
import { createDrawerNavigator, DrawerContentScrollView, DrawerItem } from '#react-navigation/drawer'
import { createStackNavigator } from '#react-navigation/stack'
import { AppearanceProvider } from 'react-native-appearance'
const Drawer = createDrawerNavigator()
const Stack = createStackNavigator()
const HomeScreen = ({ navigation, route }) => {
return(
<View style={styles.container}>
<Text>Home Screen </Text>
</View>
)
}
const CategoryScreen = ({ navigation, route }) => {
return(
<View>
<Text>Category Screen </Text>
<Text>{JSON.stringify(route)}</Text>
</View>
)
}
const ProductScreen = ({ navigation, route }) => {
return(
<View>
<Text>Product Screen </Text>
<Text>{JSON.stringify(route)}</Text>
</View>
)
}
const CustomDrawerContent = ({ props }) => {
return (
<DrawerContentScrollView {...props}>
<DrawerItem
label="Home"
onPress={() => props.navigation.navigate('Home')}
/>
<DrawerItem
label="Category 1"
onPress={() =>
props.navigation.navigate('Main', {
Screen: 'Category',
params: { id: 1 },
})
}
/>
<DrawerItem
label="Category 2"
onPress={() =>
props.navigation.navigate('Main', {
Screen: 'Category',
params: { id: 101 },
})
}
/>
</DrawerContentScrollView>
)
}
const MainNavigator = () => {
return (
<Stack.Navigator>
<Stack.Screen name="Category" component={CategoryScreen} />
<Stack.Screen name="Product" component={ProductScreen} />
</Stack.Navigator>
)
}
const ApplicationNavigator = () => {
return (
<NavigationContainer initialRouteName="Home">
<Drawer.Navigator
drawerContent={(props) => {
return <CustomDrawerContent props={props} />
}}
>
<Drawer.Screen
name="Home"
component={HomeScreen}
/>
<Drawer.Screen
name="Main"
component={MainNavigator}
/>
</Drawer.Navigator>
</NavigationContainer>
)
}
export default function App() {
return <ApplicationNavigator />
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
backgroundColor: '#FFFFFF',
},
});
UPDATE
I have noted that if I initialize the params (blank, with values, whichever) outside of the custom drawer content first, the above code begins to work as expected.
Very simple and silly fix that a rubber duck could solve.
Screen !== screen. I was passing in an unknown param to navigate.

Why is the bottom tab navigator height smaller (squished) upon reload?

I am working with the createBottomTabNavigator() from React Navigation in my Expo application. I have 3 different tabs for different screen routing. Each tab consists of the screen name and an icon.
However, I am experiencing some inconsistent behavior after I reload the screen using expo. On the home screen load, the height of the bottom tab navigator appears to be "squished".
Squished Behavior: Notice in the picture below that the icons are to the left of the text and how thin the navigation bar is.
Normal Behavior: Now notice the "regular" behavior once I double click the home button or do some action to refresh the screen.
I can even navigate through the 3 navigation pages and it will stay in this "squished" state until I do something like double click home button (on iPhone this brings up your open applications - similar to the bottom right square Android button).
The code for the bottom tab navigation is below. There isn't anything fancy with it, just pretty standard stuff from the documentation.
import React from 'react';
import { StyleSheet } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { AntDesign, Ionicons } from '#expo/vector-icons';
const Tab = createBottomTabNavigator();
const HomeTab = createStackNavigator();
const UserProfileTab = createStackNavigator();
const NutritionTab = createStackNavigator();
function UserProfileScreen() {
return (
<UserProfileTab.Navigator
screenOptions={{
headerStyle: {
backgroundColor: Colors.topNavBar
},
headerTitleAlign: 'center'
}}>
<UserProfileTab.Screen name="Profile" component={UserProfile} />
</UserProfileTab.Navigator>
);
}
function NutritionScreen() {
return (
<NutritionTab.Navigator
screenOptions={{
headerStyle: {
backgroundColor: Colors.topNavBar
},
headerTitleAlign: 'center'
}}>
<NutritionTab.Screen name="Nutrition" component={Nutrition} />
</NutritionTab.Navigator>
);
}
function BottomTabs() {
return (
<>
<SafeAreaView style={{ flex: 0, backgroundColor: Colors.topNavBar }} />
<SafeAreaView style={{ flex: 1, backgroundColor: Colors.topNavBar }}>
<Tab.Navigator
initialRouteName="Home"
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = focused ? 'home' : 'home';
} else if (route.name === 'Profile') {
iconName = focused ? 'user' : 'user';
} else if (route.name === 'Nutrition') {
iconName = focused ? 'md-nutrition' : 'md-nutrition';
}
if (route.name === 'Nutrition') {
return <Ionicons name={iconName} size={size} color={color} />
}
return <AntDesign name={iconName} size={size} color={color} />;
},
})}
tabBarOptions={{
activeTintColor: Colors.bottomNavBarActive,
inactiveTintColor: Colors.bottomNavBarInactive,
tabStyle: styles.tabStyle,
labelStyle: styles.tabLabel,
}}>
<Tab.Screen
name="Nutrition"
component={NutritionScreen}
options={{ tabBarLabel: 'Nutrition' }}
/>
<Tab.Screen
name="Home"
component={MainStackNavigator}
/>
<Tab.Screen
name="Profile"
component={UserProfileScreen}
options={{ tabBarLabel: 'Profile' }}
/>
</Tab.Navigator>
</SafeAreaView>
</>
)
}
// Styling
const styles = StyleSheet.create({
tabStyle: {
backgroundColor: Colors.boxColor,
},
tabLabel: {
fontSize: 14,
},
});
export default function App() {
return (
<NavigationContainer>
<BottomTabs />
</NavigationContainer>
);
}
Has anyone ever faced an issue like this? Is it just some weird glitch with Expo?

Open a model box when clicking header right button in react navigation stack

This is the app.js stack screen element
<Stack.Screen name="Home" component={HomeScreen} options={
{ headerTitleStyle: { alignSelf: 'center' },
headerRight: () => (
<Button
onPress={this.add}
title="+"
/>
)
}
}/>
Add function in app.js
add = async () => {
this.setState({
isVisible: true
})
}
Modal is present in homeScreen component
<Modal visible={this.state.isVisible} animationType={"slide"}
onRequestClose={() => {this.props.isVisible=false} }>
Whenever the app is loading, it's going to the modal page. It's not rendering homescreen view.