Having two tab navigation bars in React Navigation - react-native

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);

Related

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

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>
);
}

React Native — React-Navigation back button on wrong side of appbar?

Okay so I'm just jumping into React Native and I'm going through the docs with the react-navigation package. Whenever a screen is pushed onto the stack, the animation goes from right-left by default—Also I'm noticing the back button is on the right side of the appbar instead of the left be default. Is this by design or have I set something up incorrectly?
Also ignore the FC I'm using, I know it's not recommended but I'm just getting a feel for RN 😅
See image and code below:
import { StatusBar } from "expo-status-bar";
import { Button, StyleSheet, Text, View } from "react-native";
import { createNativeStackNavigator } from "#react-navigation/native-stack";
import { BaseNavigationContainer } from "#react-navigation/native";
import { FC } from "react";
import { StackScreenProps } from "./Types";
const Home: FC<StackScreenProps> = ({ navigation }) => {
return (
<View style={styles.container}>
<Text>Hello World </Text>
<Button
title="Switch Page"
onPress={() => {
navigation.navigate("About");
}}
/>
</View>
);
};
const About: FC<StackScreenProps> = ({ navigation }) => {
return (
<View style={styles.container}>
<Text>Learn the Process First</Text>
<Button title="Go Back" onPress={() => navigation.goBack()} />
</View>
);
};
const Stack = createNativeStackNavigator();
export default function App() {
return (
<BaseNavigationContainer>
{/* #ts-ignore */}
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="About" component={About} />
</Stack.Navigator>
</BaseNavigationContainer>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});

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.

Basic simple React Navigation v5 navigating between screens

I've spent 2 days search, reading and find lots of v3 and v4 class based examples for how to handle navigation in React Native Navigation.
All I want to achieve is to move between 2 of my screens using react native navigation. My App.js contains the Tab navigator and that works fine. The tab opens up a component (screen) called Mens and from there I want to be able to open up a PDP page that passes in properties of an article ID.
I have tried numerous ways of wiring up the application to allow this; I've read all the react native documentation and tried a number of approaches;
Created a seperate file to include the naviagtion stack;
import * as React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import { createStackNavigator } from '#react-navigation/stack'
import News from './news';
import Mens from './mens'
import Watch from './watch'
const Stack = createStackNavigator()
function MainStackNavigator() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="News" component={News} />
<Stack.Screen name="Mens" component={Mens} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default MainStackNavigator
But when I try to use one of the screens, I get an error. The onpress I try is;
<TouchableOpacity onPress={() => navigation.navigate('Mens')}>
I have also tried to move the NavigationContainer / Stack Navigator code into the News component, but I haven't manage to make that work.
The flow that I want is simple enough; App.js has my tabs, 5 tabs that navigate to the main screens and then on each of those, people can click on a list item in a flat list (which displays a summary) to read the full article.
The news.js file content is below;
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, FlatList, Image, ListItem, Text, View, StyleSheet, ScrollView, TouchableOpacity, Alert} from 'react-native';
import Moment from 'moment';
import MatchReports from './matchreports.js';
import navigation from '#react-navigation/native';
const News = (props) => {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
function chkValue(val) {
if(val.length == 0){
val = 'https://derbyfutsal.files.wordpress.com/2019/07/banner-600x300.png';
}else{
val = val;
}
return val;
}
useEffect(() => {
fetch('https://xxxx')
.then((response) => response.json())
.then((json) => {
setData(json)
})
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
return (
<ScrollView>
<View style={styles.body}>
<Text style={styles.titlesnopadding}>Watch</Text>
<View style={{height:200}}>
<Watch />
</View>
<Text style={styles.titles}>Match reports</Text>
<View style={{height:100}}>
<MatchReports typeOfProfile='Men'/>
</View>
<Text style={styles.titles}>Latest News</Text>
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<View>
<TouchableOpacity onPress={() => navigation.navigate('Mens')}>
<Image style={styles.img} source={{ uri: chkValue(item.jetpack_featured_media_url) }} />
<View>
<Text style={styles.textbck}>{item.title.rendered.replace(/<\/?[^>]+(>|$)/g, "")}</Text>
<Text style={styles.summary}>{item.excerpt.rendered.replace(/<\/?[^>]+(>|$)/g, "")}{"\n"}{Moment(item.date, "YYYYMMDD").fromNow()}</Text>
</View>
</TouchableOpacity>
</View>
)}
/>
)}
</View>
</ScrollView>
);
};
Any help is appreciated, as I've read so many using class instead of functional programming and out of date navigation that it's been challenging working it out.
EDIT:
I had missed the props.navigation.navigate('Mens'), which works fine now. Though its only half solves my problem.
I have the following inside my app.js;
import 'react-native-gesture-handler';
import React from 'react';
import { View, StyleSheet, Image } from 'react-native';
import News from './components/news';
import Shop from './components/shop';
import Mens from './components/mens';
import Fixtures from './components/fixtures';
import Ladies from './components/ladies';
import Pdp from './components/pdp'
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
const App = () => {
return (
<View style={styles.header}>
<View>
</View>
<View style={styles.body}>
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="News" component={News} />
<Tab.Screen name="Mens" component={Mens} />
<Tab.Screen
name="icon"
component={Shop}
options={{
title: '',
tabBarIcon: ({size,focused,color}) => {
return (
<Image
style={{ marginTop:20,width: 80, height: 80 }}
source={{
uri:
'https://derbyfutsal.files.wordpress.com/2020/05/derby-futsal-logo-2019.png',
}}
/>
);
},
}}
/>
<Tab.Screen name="Ladies" component={Ladies} />
<Tab.Screen name="Fixtures" component={Fixtures} />
</Tab.Navigator>
</NavigationContainer>
</View>
</View>
)
};
const styles = StyleSheet.create({
header: {
marginTop: 20,
height:0,
flex: 1
},
body: {
flex:2,
flexGrow:2,
},
nav: {
fontSize: 20,
},
});
export default App;
Anything thats been set as tab Screen in this works just fine if I reference it in my news.js screen, but I don't want to declare PDP.js in this as I don't want it to display as a tab.
Instead once a user has gone to a screen using the tab navigation, the user then clicks on a item in the flatlist and it opens up pdp.js.
In many ways, once someone has opened up the main categories (as seen on the tab navigation) and clicked on an item in the flatlist, all I want to do is;
<a href="pdp.js?id=xxxxx">
https://reactnavigation.org/docs/navigation-actions/#navigate
import { CommonActions } from '#react-navigation/native';
navigation.dispatch(
CommonActions.navigate({
name: 'Profile',
params: {
user: 'jane', // props.route.params.user
},
})
);

Navigation back click event in React Native

I am working on a React Native application. I am using navigation in my application. I want to do something when user presses back navigation i.e. moved to a back screen.
How can i get the click event of "blacked circle Frage" in the above image. I am working on IOS
Use a custom header with
import { Header } from "native-base";
And add below code in your route file to disable default header.
navigationOptions: {
header: null
}
my custome header code for your reference
<Header style={styles.header}>
<View style={{ flex: 2 }}>
<TouchableOpacity
style={styles.iconButton}
onPress={() => { this.createNote(); this.props.navigation.navigate('Home') }}>
<Icon name="arrow-back" size={28} color="#606060" />
</TouchableOpacity>
</View>
<View style={{ flex: 8 }}></View>
<View style={{ flex: 2 }}>
<TouchableOpacity
style={styles.iconButton}
onPress={() => { this.createNote(); this.props.navigation.navigate('Home') }}>
<Icon name="check" size={28} color="#606060" />
</TouchableOpacity>
</View>
</Header>
reference link:- https://www.npmjs.com/package/native-base
It probably varies depending on the libraries you are using. I am using react-native-paper in Expo, which uses the headerLeft option in the Stack.Screen component. Here's a complete example - save it and then 'expo start'
import { Provider as PaperProvider, Text } from 'react-native-paper'
import { NavigationContainer } from '#react-navigation/native'
import { createNativeStackNavigator } from '#react-navigation/native-stack';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<PaperProvider>
<NavigationContainer >
<Stack.Navigator>
<Stack.Screen
name="Example"
options={{
title: 'Example',
headerLeft: () => <Text>Custom left button</Text>,
}}
component={() => <Text>Example body text</Text>}
/>
</Stack.Navigator>
</NavigationContainer>
</PaperProvider>
)
}
You can use onPress={() => this.props.navigation.goBack()} on TouchableOpacity if you are redirecting to the previous page
Also you can use this.props.navigation.navigate('Any_Screen') to move to other screens.
Also, I would like to suggest you to get familiar with BackHandler to move back to previous page when hardware back button is pressed.
add the code
onClick={this.props.navigation.goBack()}
or use specif navigation replace go back to
onClick={this.props.navigation.navigate('namepagespacific')}
check this screen there are mutiple example of handling click event
import React from 'react';
import { View, Text, StyleSheet, Button} from 'react-native';
class DetailsScreen extends React.Component {
static navigationOptions = ({ navigation, navigationOptions, screenProps }) => {
return {
title: navigation.getParam('title', 'A Nested Details Screen'),
};
};
render() {
const { navigation } = this.props;
const itemId = navigation.getParam('itemId', 'NO-ID');
const otherParam = navigation.getParam('otherParam', 'some default value');
return (
<View style={styles.detailsScreen}>
<Text>Details Screen</Text>
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Text>otherParam: {JSON.stringify(otherParam)}</Text>
<Button
title="Go to Details... again"
onPress={() => this.props.navigation.push('Details')}
/>
<Button
title="Go to Home"
onPress={() => this.props.navigation.navigate('Home')}
/>
<Button
title="Go back"
onPress={() => this.props.navigation.popToTop()}
/>
<Button
title="Update the title"
onPress={() => this.props.navigation.setParams({ title: 'Updated!' })}
/>
<Button
title="Modal"
onPress={() => this.props.navigation.navigate('MyModal')}
/>
</View>
);
}
}
const styles = StyleSheet.create({
detailsScreen: {
flex: 1,
alignItems: "center",
justifyContent: "center"
}
})
export default DetailsScreen;
things you have asked in the comment section I could not find any exact answer for your question but you can take a look into this url how header buttons work
https://snack.expo.io/#react-navigation/simple-header-button-v3
hope this will work for you
header: ({ goBack }) => ({
left: ( <Icon name={'chevron-left'} onPress={ () => { goBack() } } /> ),
}),
you can also follow this page https://github.com/react-navigation/react-navigation/issues/779