How to place element over Bottom Tabs Navigator in React Native - reactjs

I want to place Facebook ads to React native using react-native-fbads. I am using Bottom tab navigator in my application and I want the ad to be fixed floating over the bottom tab.
I can place the ad on every tab screen but I don't want to place the ad on every screen and instead, I want to place it just over the bottom tab so that it is visible on every tab user visits.
Something like this:
Code: TabNavigator.js
<Tab.Navigator
screenOptions={({ route }) => ({
headerShown: false,
})}
tabBarOptions={{
activeTintColor: '#001B79',
inactiveTintColor: 'gray',
}}
>
<Tab.Screen name="Home1" component={Home11} />
<Tab.Screen name="Home2" component={Home12} />
</Tab.Navigator>
I want to add <BannerAd /> so that it will work like I want it to be.

just try to use bottom-tab-navigator! regularly!
// ...
import { View, Text, TouchableOpacity } from 'react-native';
function MyTabBar({ state, descriptors, navigation }) {
return (
<View style={{ flexDirection: 'row' }}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
// The `merge: true` option makes sure that the params inside the tab screen are preserved
navigation.navigate({ name: route.name, merge: true });
}
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
return (
<TouchableOpacity
accessibilityRole="button"
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
onLongPress={onLongPress}
style={{ flex: 1 }}
>
<Text style={{ color: isFocused ? '#673ab7' : '#222' }}>
{label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
}
<Tab.Navigator tabBar={props => <MyTabBar {...props} />}>
{...}
</Tab.Navigator>

Related

React Native Stack Navigation not working

I am building a fitness app, and currently I am having a problem with React Native navigation stack, that I wasn't having before and not sure why this is happening.
As you can see in the video, after you select the type of training and number of days, it should go to the screen with the filtered workouts (WorkoutSelection).
Edit: I tried moving the navigation.navigate() outside the async function and it does navigate to the correct page, so I'm assuming it has something to do with it being inside a async function.
Here's the code for the confirm button:
async function confirmHandler() {
setIsLoading(true);
try {
const data = await getTrainingData(
selectedTrainingData.value,
selectedDaysData.value
);
setFilteredWorkouts(data);
setIsLoading(false);
navigation.navigate('WorkoutSelection');
} catch (error) {
console.log(error);
setIsLoading(false);
return (
<ErrorScreen message={'Something went wrong. Please try again.'} />
);
}
}
What is happening right now is whenever I click the confirm button, it goes to the home screen.
It does fetch the data from the API, I logged it to the console and it's working so, that shouldn't be the problem.
Here's the navigation code:
function TrainingOptionsStack() {
return (
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: GlobalStyles.colors.background },
headerTintColor: 'white',
headerTitleStyle: {
fontFamily: 'open-sans-semi-bold',
},
}}
>
<Stack.Screen
name='WorkoutsScreen'
component={WorkoutsScreen}
options={{ title: 'Workouts' }}
/>
<Stack.Screen
name='SelectPhase'
component={BlockOptions}
options={{ title: 'Training Phase' }}
/>
<Stack.Screen
name='WorkoutSelection'
component={WorkoutSelection}
options={{
title: 'Select a workout',
}}
/>
<Stack.Screen
name='SelectDay'
component={SelectDay}
options={{ title: 'Select Day' }}
/>
<Stack.Screen name='WorkoutOfTheDay' component={WorkoutOfTheDay} />
<Stack.Screen
name='PreviewModal'
component={PreviewModal}
options={{ presentation: 'modal', title: false }}
/>
</Stack.Navigator>
);
}
function AppNavigation() {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={{
headerStyle: { backgroundColor: GlobalStyles.colors.background },
headerTintColor: 'white',
tabBarStyle: {
backgroundColor: GlobalStyles.colors.primary700,
paddingTop: 5,
height: 90,
},
headerTitleStyle: {
fontFamily: 'open-sans-semi-bold',
},
tabBarActiveTintColor: 'white',
}}
>
<Tab.Screen
name='Home'
component={HomeScreen}
options={{
title: 'Home',
tabBarIcon: ({ focused }) => {
return (
<Ionicons
name='home-outline'
size={34}
color={
focused
? GlobalStyles.colors.primary400
: GlobalStyles.colors.primary500
}
/>
);
},
}}
/>
<Tab.Screen
name='Workouts'
component={TrainingOptionsStack}
options={{
title: 'Workouts',
tabBarIcon: ({ focused }) => {
return (
<Ionicons
name='barbell-outline'
size={34}
color={
focused
? GlobalStyles.colors.primary400
: GlobalStyles.colors.primary500
}
/>
);
},
headerShown: false,
}}
/>
<Tab.Screen
name='RepMaxCalculator'
component={RepMaxCalculator}
options={{
title: 'Max Rep Calculator',
tabBarIcon: ({ focused }) => {
return (
<Ionicons
name='calculator-outline'
size={30}
color={
focused
? GlobalStyles.colors.primary400
: GlobalStyles.colors.primary500
}
/>
);
},
}}
/>
<Tab.Screen
name='ProgressChart'
component={ProgressChart}
options={{
title: 'Progress Chart',
tabBarIcon: ({ focused }) => {
return (
<Ionicons
name='bar-chart-outline'
size={30}
color={
focused
? GlobalStyles.colors.primary400
: GlobalStyles.colors.primary500
}
/>
);
},
}}
/>
</Tab.Navigator>
</NavigationContainer>
);
}
And here's the 'WorkoutSelection' component -
import React, { useEffect } from 'react';
import { FlatList, StyleSheet, View } from 'react-native';
import SelectWorkout from '../components/SelectWorkout';
import FlatButton from '../components/UI/buttons/FlatButton';
import { GlobalStyles } from '../constants/styles';
import useAppContext from '../store/AppContext';
import { doc, setDoc, serverTimestamp } from 'firebase/firestore';
import { db } from '../firebase/firebaseConfig';
function WorkoutSelection({ navigation }) {
const {
filteredWorkouts,
previewWorkoutHandler,
setWorkoutPreviewTitle,
setCurrentWorkout,
currentWorkout,
userIsAuthenticated,
} = useAppContext();
// useEffect(() => {
// const docRef = doc(db, 'users', userIsAuthenticated.uid);
// async function addCurrentWorkoutToDataBase() {
// await setDoc(docRef, 'CurrentWorkout', currentWorkout, { merge: true });
// }
// addCurrentWorkoutToDataBase();
// }, [currentWorkout]);
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<FlatButton
style={styles.headerButton}
onPress={() => navigation.replace('WorkoutsScreen')}
>
Cancel
</FlatButton>
),
});
}, []);
const id = filteredWorkouts
.flatMap((item) => item.workouts)
.flatMap((item) => item.id);
function previewHandler(item) {
previewWorkoutHandler(id[0]);
setWorkoutPreviewTitle(item.title);
navigation.navigate('PreviewModal');
}
function selectWorkoutHandler(item) {
setCurrentWorkout([item]);
navigation.navigate('WorkoutsScreen');
}
return (
<View style={styles.rootContainer}>
<FlatList
data={filteredWorkouts}
renderItem={({ item }) => (
<SelectWorkout
name={item.title}
onSelect={() => selectWorkoutHandler(item)}
onShowPreview={() => previewHandler(item)}
/>
)}
keyExtractor={(item) => item.id}
/>
</View>
);
}
export default WorkoutSelection;
const styles = StyleSheet.create({
rootContainer: {
backgroundColor: GlobalStyles.colors.background,
flex: 1,
},
headerButton: {
textDecorationLine: 'none',
marginRight: 24,
fontSize: 16,
fontFamily: 'open-sans-semi-bold',
},
});
Here's a little video to demonstrate the problem -
https://drive.google.com/file/d/1OoKlj2tZsL6sYqpcUnKUcQhAjmhTZGJZ/view?usp=share_link
What I expect is to go to the 'WorkoutSelection" screen so the user can complete the workout selection flow and I can update it in Firebase.
Appreciate any help!
Have you import all the component on above page?
e.g. if you want to use WorkoutSelection component then import that.
like this.
import {WorkoutSelection} from 'path here'.
and then use that component for the navigation.
Hope you got my point.
It is happening because you are using replace function instead of goBack or navigate in WorkoutSelection
So first change from replace to goBack or navigate
after selecting values WorkoutSelection , if you want to refresh WorkoutsScreen so your selected filter can be apply then you have two ways to do that
call api on focus change in WorkoutsScreen (follow below mentioned link )
https://reactnavigation.org/docs/function-after-focusing-screen/
fire event from WorkoutSelection before calling goBack or navigate and that event should be listened on WorkoutsScreen so when you get event , you can call api and refresh screen as per filter selection
I figured out the problem. I had to go back a few commits and start adding one thing at a time to try to get to the point where I was having the same problem.
The problem was in the App.js, the rootFunction that I created wasn't returning anything conditionally as I wanted. I had to restructure it a bit and got it working.
Totally missed it. Thanks all for the help!

Why my Bottom Tabs Navigator is re rendering unnecessary?

I am making an app in which there are three bottom tabs screen. HomeScreen, CartScreen and Profile Screen. I was wanting to check the re renders of my app using "Why did you re render" npm package and found a critical bug or is it so.
The MainTabScreen(The screen in which all the bottom tabs are nested) is re rendering as many times I change the screen, just because its props changed.
Here is the console log :
Here is the code of the MainTabScreen.js:
const MainTabScreen = () => {
return (
<Tab.Navigator
tabBarOptions={{
showLabel: false,
style: {
position: "absolute",
height: 40,
bottom: 0,
right: 0,
left: 0,
backgroundColor: "#fff",
},
}}
>
<Tab.Screen
name="MainHome"
component={MainHomeScreen}
options={{
tabBarIcon: ({ focused }) => (
<Icon.Home
// color={"#f62459"}
color={focused ? "#f62459" : "#302f2f"}
height={28}
width={28}
/>
),
}}
/>
<Tab.Screen
name="MainCart"
component={MainCartScreen}
options={{
tabBarIcon: ({ focused }) => (
<Icon.ShoppingCart
// color="#302f2f"
color={focused ? "#f62459" : "#302f2f"}
width={28}
height={28}
/>
),
}}
/>
<Tab.Screen
name="MainProfile"
component={MainProfileScreen}
options={{
tabBarIcon: ({ focused }) => (
<Icon.User
// color="#302f2f"
color={focused ? "#f62459" : "#302f2f"}
height={28}
width={28}
/>
),
}}
/>
</Tab.Navigator>
);
};
export default MainTabScreen;
Edit 11:27 05-09-21
The MainTabScreen renders inside AppStack screen which renders inside the AuthStack.js for authentication and this AuthStack.js renders inside the App.js
Here is the AppStack.js code:
const AppStack = () => {
return(
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="MainTabScreen" component={MainTabScreen} />
<Stack.Screen name="Product" component={Product} />
<Stack.Screen name="OrderScreen" component={OrderScreen} />
</Stack.Navigator>
)
This is the AuthStack.js
import React, {
useState,
useEffect,
useMemo,
useReducer,
useContext,
} from "react";
import { View, Text, Platform } from "react-native";
import AppStack from "./navigation/AppStack";
import RootStack from "./navigation/RootStack";
import { AuthContext } from "./context";
import * as SecureStore from "expo-secure-store";
import LottieView from "lottie-react-native";
import { useIsMounted } from "../screens/useIsMounted";
const AuthStack = () => {
const initialLoginState = {
isLoading: true,
accessToken: null,
};
// The rest of the authentication logic...
return (
<AuthContext.Provider value={authContext}>
{loginState.accessToken !== null ? <AppStack /> : <RootStack />}
</AuthContext.Provider>
);
};
export default AuthStack;
App.js
import React from "react";
import { StyleSheet, Text, View, Dimensions } from "react-native";
import { NavigationContainer } from "#react-navigation/native";
import store from "./redux/store";
const App = () => {
return (
<Provider store={store}>
<NavigationContainer>
<AuthStack />
</NavigationContainer>
</Provider>
);
};
export default App;
i found the solution...
use:
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
`<Tab.Navigator
tabBar={props => <MyTabBar {...props} />}>`
create separately TabBar (for example):
function MyTabBar({ state, descriptors, navigation, position }) {
const edge = useSafeAreaInsets();
const { colors } = useTheme();
return (
<View style={{ flexDirection: 'row', height: edge.bottom + 70, alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.cell }}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
// The `merge: true` option makes sure that the params inside the tab screen are preserved
navigation.navigate({ name: route.name, merge: true });
}
};
if (label === 'Browse Radios') {
return <CenterTabBar isFocused={isFocused} onPress={() => { onPress(); }} />
} else {
return <MyTabBarButton isFocused={isFocused} onPress={() => { onPress(); }} label={label} />
}
})}
</View>
);
}
and create separately buttons for TabBar (for example):
const CenterTabBar = ({ onPress, isFocused }) => {
return (
<TouchableOpacity style={{
alignItems: 'center',
flex: 1
}}
onPress={() => {
onPress();
}}
>
{!isFocused && <Image style={styles.centerTabBar} source={require('../../assets/images/tab_browse.png')} />}
{isFocused && <Image style={styles.centerTabBar} source={require('../../assets/images/tab_browse_.png')} />}
</TouchableOpacity>
);
}
const MyTabBarButton = ({ onPress, isFocused, label }) => {
const { colors, dark } = useTheme();
return (
<TouchableOpacity style={{
alignItems: 'center',
flex: 1
}}
onPress={() => { onPress() }}
>
<Animated.Image style={[styles.icon_tab, { tintColor: isFocused ? '#00ACEC' : colors.tint }]} source={GetIconTab(label)} />
{isFocused && <Animated.View style={styles.circle} entering={BounceIn} />}
</TouchableOpacity>
);
}

Navigation with parameters from custom element in Flatlist in React Native: Empty parameters

After not succeeding in solving my problem here, I had to create a new question with all my classes in it.
I am new to react native and have a problem figuring out how to navigate from one class to another one with passing parameters and would appreciate your help.
All I want to do is:
SessionCreate with flatlist containing CustomButton
Navigate from SessionCreate to ItemConfig by clicking CustomButton
Pass parameter "element" to ItemConfig
Show content of parameter passed in ItemConfig
With this setup an empty "element" is passed as parameter to the ItemConfigScreen (but no error occurs):
app.js:
//Views
function Home({ navigation }) {
return (
<HomeScreen />
);
}
function Session({ navigation }) {
return (
<SessionScreen />
);
}
//subviews
function SessionCreate({ navigation }) {
return (
<SessionCreateScreen />
);
}
function ItemConfig({ navigation }) {
return (
<ItemConfigScreen />
);
}
//navigation stacks
const SessionStack = createStackNavigator();
function SessionStackScreen({ navigation }) {
return (
<SessionStack.Navigator>
<SessionStack.Screen
name="Session"
component={Session}
options={{ tabBarLabel: 'Session!' }}
/>
<SessionStack.Screen
name="SessionCreate"
component={SessionCreate}
options={{ tabBarLabel: 'SessionCreate!' }}
/>
<SessionStack.Screen
name="ItemConfig"
component={ItemConfig}
options={{ tabBarLabel: 'ItemConfig!' }}
/>
</SessionStack.Navigator>
);
}
//Navbar Bottom
const Tab = createBottomTabNavigator();
function App() {
return (
<View style={[theme.colcontainer, { flexDirection: "column" }]} >
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = focused ? 'home' : 'home-outline';
} else if (route.name === 'Session') {
iconName = focused ? 'book' : 'book-outline';
}
// dynamic ionicon
return <Ionicons name={iconName} size={size} color={color} />;
},
})}
>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Session" component={SessionStackScreen} />
</Tab.Navigator>
</NavigationContainer>
</View >
);
}
export default App;
SessionScreen.js:
function SessionScreen() {
const navigation = useNavigation();
return (
<View style={[theme.container, { flexDirection: "column" }]} >
<View>
<TouchableOpacity onPress={() => navigation.navigate('SessionCreate')}>
<Text >Create Session</Text>
</TouchableOpacity>
</View>
</View >
);
}
export default SessionScreen;
SessionCreateScreen.js:
//data
const sessionElements = [
{
id: "1",
title: "title1"
}
];
function SessionCreateScreen() {
const navigation = useNavigation()
const renderItemConfiguration = ({ item }) => (
<CustomButton element={item.title} onPress={() => navigation.navigate('ItemConfig', { element: 'item.title' })} />
);
return (
<View style={{ flexDirection: "column", flex: 2}} >
<SafeAreaView >
<FlatList
data={sessionElements}
renderItem={renderItemConfiguration}
keyExtractor={(item) => item.id}
/>
</SafeAreaView>
</View >
);
}
export default SessionCreateScreen;
ItemConfigScreen.js:
const element = "";
function ItemConfigScreen() {
return (
<ScrollView >
<View style={{ flexDirection: "column", flex: 2}} >
<Text>Configure {element} here</Text>
</View >
</ScrollView>
);
}
export default ItemConfigScreen;
Any help is appreciated.
To get parameters in ItemConfigScreen you have to use the useRoute hook from the react-navigation package.
you can read more about it here useRoute
import {useRoute} from '#react-navigation/native';
function ItemConfigScreen() {
const route = useRoute();
const element = route.params?.element;
return (
<ScrollView >
<View style={{ flexDirection: "column", flex: 2}} >
<Text>Configure {element} here</Text>
</View >
</ScrollView>
);
}
There is also a mistake in your onPress navigation call in CustomButton, instead of 'item.title' you will have to pass ${item.title} then only actual data will be passed. JS Template Literals
<CustomButton element={item.title} onPress={() => navigation.navigate('ItemConfig', { element: `${item.title}` })} />

createDrawerNavigator and back button

We're using drawer navigator in our app to show the routes in the drawer. Our routes have stacks of pages and users can go into a page. E.g.:
Home
HomeChild1
HomeChild2
About
When user goes to the HomeChild1 or HomeChild2 we want to show back button in the header. We don't want to add a back button in each page's navigation options like below:
HomeScreen1.navigationOptions = ({ navigation }) => {
const { state } = navigation;
return {
title: `Home Screen 1`,
headerLeft: (
<Icon
name="ios-arrow-back"
type="ionicon"
color="#FFF"
underlayColor="transparent"
iconStyle={{ paddingRight: 5 }}
onPress={() => {
navigation.navigate.goBack();
}}
/>
)
};
}
Is there a way to put this config at the global level - like in the defaultNavigationOptions.
const defaultNavigationOptions = ({ navigation }) => {
return {
hideStatusBar: false,
headerStyle: {
backgroundColor: Colors.baseColor
},
headerTintColor: Colors.titleColor,
headerBackImage: ( //this has no affect
<Icon
name="ios-arrow-back"
type="Ionicons"
style={{ color: "#D8025E", fontSize: 30, paddingHorizontal: 10 }}
/>
),
headerLeft: (
<Icon
name="menu"
size={30}
style={{ marginStart: 25 }}
color="#FFF"
backgroundColor="#FFF"
onPress={() => navigation.openDrawer()}
/>
)
};
};
You can set up using navigation path values.
defaultNavigationOptions: ({ navigation }) => ({
headerLeft: () => {
const { routeName } = navigation.state;
let iconName;
if (routeName === "HomeChild1") {
iconName = "ios-arrow-back";
} else if (routeName === "HomeChild2") {
iconName = "ios-arrow-back";
}
....
return (
<Icon
name={iconName}
size={30}
style={{ marginStart: 25 }}
color="#FFF"
backgroundColor="#FFF"
onPress={() => navigation.goBack()}
/>
);
...

React-Native TabNavigator and Modals

I'm trying to use this library to show a custom modal dialog. I've a StackNavigator with three screens and on of these, the MainScreen, is a TabNavigator on which I set up the following header:
static navigationOptions = ({navigation}) => {
const { params } = navigation.state
return {
headerRight: (
<Content>
<Grid>
<Col style={styles.headerButton}>
<TouchableHighlight style={styles.infoButton} onPress={() => {params._onAbout()}} underlayColor='lightgrey'>
<Icon ios='ios-information-circle' android='md-information-circle' style={{fontSize: 24}} />
</TouchableHighlight>
</Col>
<Col style={styles.headerButton}>
<TouchableHighlight style={styles.logoutButton} onPress={() => {params._onLogout()}} underlayColor='lightgrey'>
<Icon ios='ios-exit-outline' android='md-exit' style={{fontSize: 24}} />
</TouchableHighlight>
</Col>
</Grid>
</Content>
)
}
}
The second button opens a simple Alert (from react-native). With the first button I would open a custom modal to show app and developer details.
The main screen has the following render method;
render(): JSX.Element {
return (
<TabContent />
)
}
where TabContent is simply my tabs configuration:
const TabContent: NavigationContainer = TabNavigator({
Courses: {
screen: CoursesScreen,
navigationOptions: ({ navigation }) => ({
// title: `${navigation.state.params.user}'s Courses`,
tabBarLabel: 'Corsi',
tabBarIcon: ({ tintColor, focused }) => (
<Icon ios={focused ? 'ios-calendar' : 'ios-calendar-outline'} android='md-calendar' style={{fontSize: 18, color: tintColor}} />
)
})
},
Profile: {
screen: ProfileScreen,
navigationOptions: ({ navigation }) => ({
// title: `${navigation.state.params.user}'s Profile`,
tabBarLabel: 'Profilo',
tabBarIcon: ({ focused, tintColor }) => (
<Icon ios={focused ? 'ios-person' : 'ios-person-outline'} android='md-person' style={{fontSize: 18, color: tintColor}} />
)
})
}
}, {
tabBarOptions: {
activeTintColor: '#F3E03B',
showIcon: true,
labelStyle: {
fontWeight: 'bold'
},
style: {
backgroundColor: 'black'
}
}
})
The library linked above requires a layout like this:
<View style={styles.wrapper}>
<Modal style={[styles.modal, styles.modal3]} position={"center"} ref={"modal3"} isDisabled={this.state.isDisabled}>
<Text style={styles.text}>Modal centered</Text>
<Button onPress={() => this.setState({isDisabled: !this.state.isDisabled})} style={styles.btn}>Disable ({this.state.isDisabled ? "true" : "false"})</Button>
</Modal>
</View>
but if I put TabContent tab inside that view the tab navigator doesn't work anymore.
Is there a way to make my TabNavigator and Modal from that library work together?
I found a solution.
Using Container as root component allow to nest the TabContent aside other components:
render(): JSX.Element {
return (
<Container>
<Spinner visible={this.props.isLoggingOut} textContent={'Disconnessione in corso...'} textStyle={{color: '#FFF'}} />
<TabContent screenProps={{ isAdmin: this.props.isAdmin }} />
<Modal style={styles.aboutModal} position={'center'} ref={'aboutModal'} isDisabled={false}>
<Text>Modal centered</Text>
</Modal>
</Container>
)
}

Resources