I have a little problem with my bottom tabs nav. I try to follow this tutorial to customize my navbar : https://www.youtube.com/watch?v=t0GMzGgHwgk
The github of his video : https://github.com/ReactiveKoding/customtabbar/blob/main/App.js
But, when I try to test it I have an error : "Transform with key of 'translateX' must be a number", but he doesn't have it...
This is my code, if you have an issue to it I will really appreciate it !
import { ScrollView, StyleSheet, Text, View, TextInput, Image, Button, TouchableOpacity, Animated, Dimensions } from 'react-native';
import 'react-native-gesture-handler';
import { LinearGradient } from 'expo-linear-gradient';
import { useFonts } from 'expo-font';
import { Linking } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import Icon from 'react-native-vector-icons/FontAwesome';
import { useRef } from 'react';
const Tab = createBottomTabNavigator();
function User({ navigation }) {
return (
<View><Text>Construction</Text></View>
)
}
function Shower({ navigation }) {
return (
<View><Text>Construction</Text></View>
)
}
function Search({ navigation }) {
return (
<View><Text>Construction</Text></View>
)
}
function Home({ navigation }) {
return (
<View><Text>Construction</Text></View>
)
}
export default function MyTabs() {
const tabOffsetValue = useRef(new Animated.Value(0)).current;
return (
<NavigationContainer screenOptions={{ headerShown: false }}>
<Tab.Navigator screenOptions={({ route }) => ({
tabBarIcon: ({focused, color, size}) => {
let iconName;
if (route.name === 'Home') {
iconName = 'home';
} else if (route.name === 'Search') {
iconName = 'search';
} else if (route.name === 'Shower') {
iconName = 'shower';
} else if (route.name === 'User') {
iconName = 'user';
}
return <Icon name={iconName} size={size} color={color} />
},
tabBarActiveTintColor: '#2A27B0',
tabBarInactiveTintColor: '#CECECE',
tabBarShowLabel: false,
tabBarStyle: {
backgroundColor: '#fff',
height: 60
}
})}>
<Tab.Screen options={{headerShown: false}} name="Home" component={Home} listeners={({navigation, route}) => ({
tabPress: e => {
Animated.spring(tabOffsetValue, {
toValue: 0,
useNativeDriver: true
}).start();
}
})}/>
<Tab.Screen options={{headerShown: false}} name="Search" component={Search} listeners={({navigation, route}) => ({
tabPress: e => {
Animated.spring(tabOffsetValue, {
toValue: 90,
useNativeDriver: true
}).start();
}
})}/>
<Tab.Screen options={{headerShown: false}} name="Shower" component={Shower} listeners={({navigation, route}) => ({
tabPress: e => {
Animated.spring(tabOffsetValue, {
toValue: 175,
useNativeDriver: true
}).start();
}
})}/>
<Tab.Screen options={{headerShown: false}} name="User" component={User} listeners={({navigation, route}) => ({
tabPress: e => {
Animated.spring(tabOffsetValue, {
toValue: 268,
useNativeDriver: true
}).start();
}
})}/>
</Tab.Navigator>
<View style={{
width: 60,
height: 2,
backgroundColor: '#2A27B0',
position: 'absolute',
bottom: 58,
left: 15,
borderRadius: 20,
transform: [
{ translateX: tabOffsetValue }
]
}}></View>
</NavigationContainer>
)
}
I already reed this issue : How to fix "transform with key of translatex must be a number" error in React Native?
But I don't understand if my problem is the same and how I have to implement the Animated.View
Thanks by advance !
Related
I am trying to develop an app where there are two scenarios of Navigation one is before longing navigation and another is after login navigation. So And navigation {"name": "Car"} screen should only display once the user login, but when I tried to do that it says `"NAVIGATE" {"name": "Car"} Was not handled by any navigation. Please guide me on how can I solve it, Here is my code example. and on MainStackScreen there is also link to Bottom navigation.
import * as React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import BottomNavigation from './BottomNavigation';
import Car from '../src/Screen/Car/Car';
import Login from '../src/Screen/Auth/Login/Login';
import ForgetPassword from '../src/Screen/Auth/Forget/ForgetPassword';
import OfflineNotice from '../src/Screen/Offline/Offline';
import Screen from '../src/Screen/Offline/Screen';
import AsyncStorage from '#react-native-async-storage/async-storage';
function Navigation1({navigation}) {
const [age, setAge] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const readData = async () => {
try {
const userAge = await AsyncStorage.getItem('#storage_Key');
setLoading(false);
setAge(userAge);
} catch (e) {
console.log('Error While Fetching Storage data ');
}
};
React.useEffect(() => {
const interval = setInterval(() => {
readData();
}, 100);
return () => {
clearTimeout(interval);
};
}, [navigation]);
const getLageoginPage = async () => {
if (await AsyncStorage.getItem('#storage_Key')) {
} else {
navigation.navigate('Login');
}
};
React.useEffect(() => {
getLageoginPage();
}, [navigation]);
const AuthStack = createStackNavigator();
const AuthStackScreen = () => (
<AuthStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: 'white',
height: 47,
},
}}>
<Stack.Screen
name="Login"
component={Login}
options={{title: 'Dashboard', headerShown: false}}
/>
<Stack.Screen
name="Forget"
component={ForgetPassword}
options={{title: 'Dashboard', headerShown: false}}
/>
</AuthStack.Navigator>
);
const MainStack = createStackNavigator();
const MainStackScreen = () => (
<MainStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: 'white',
height: 45,
},
}}>
<MainStack.Screen
name="Home"
component={BottomNavigation}
options={{title: 'Main', headerShown: false}}
/>
<MainStack.Screen
name="Car"
component={Car}
options={{title: 'Car', headerShown: false}}
/>
</MainStack.Navigator>
);
const Stack = createStackNavigator();
let network = OfflineNotice();
if (network === false) {
return <Screen />;
}
return (
<NavigationContainer>
<Stack.Navigator>
{age !== null && !loading ? (
<Stack.Screen
name="Main"
component={MainStackScreen}
options={{title: 'Dashboard', headerShown: false}}
/>
) : (
<Stack.Screen
name="Auth"
component={AuthStackScreen}
options={{title: 'Dashboard', headerShown: false}}
/>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
export default Navigation1;
And here it is the Bottom Navigation code.
import React from 'react';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import {Dimensions} from 'react-native';
import Start from '../src/Screen/Start/Start';
import Driver from '../src/Screen/Driver/Driver';
import Ionicons from 'react-native-vector-icons/Ionicons';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
const Tab = createBottomTabNavigator();
const DEVICE_WIDTH = Dimensions.get('window').width;
const BottomNavigation = () => {
return (
<Tab.Navigator
initialRouteName={'Car'}
lazy={true}
tabBarOptions={{
showLabel: false,
inactiveTintColor: '#00A870',
backgroundColor: 'white',
activeTintColor: '#00A870',
labelStyle: {
fontSize: 12,
},
style: {
backgroundColor: 'white',
position: 'absolute',
marginLeft: 120, // Use margins as you required
marginRight: 100,
marginBottom: 20,
width: DEVICE_WIDTH - 230, // Or using a percentage as required
borderRadius: 25,
},
}}>
<Tab.Screen
name="Start"
component={Start}
options={{
tabBarLabel: 'Start',
tabBarIcon: ({focused}) => (
<MaterialCommunityIcons
name={focused ? 'steering' : 'steering-off'}
color={focused ? '#52CA00' : '#231F20'}
size={25}
/>
),
}}
listeners={({navigation, route}) => ({
tabPress: e => {
e.preventDefault();
navigation.navigate('Start');
},
})}
/>
<Tab.Screen
name="Driver"
component={Driver}
listeners={({navigation, route}) => ({
tabPress: e => {
e.preventDefault();
navigation.navigate('Driver');
},
})}
options={{
tabBarLabel: 'Driver',
tabBarIcon: ({focused}) => (
<Ionicons
name={focused ? 'map-outline' : 'map'}
color={focused ? '#52CA00' : '#231F20'}
size={25}
/>
),
}}
/>
</Tab.Navigator>
);
};
export default BottomNavigation;
Used Place On successful login.
.then(async response2 => {
setLoading(false);
// Check if user has email or not
if (response2.data.email) {
try {
await AsyncStorage.setItem('#storage_Key', data.token);
await AsyncStorage.setItem(
'#storage_Key_refresh',
data.refresh_token,
);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
try {
navigation.navigate('Car');
} catch (error) {
console.log('Navigation Error: ' + error.message);
}
} else {
setLoading(false);
Alert.alert(
'Login in successfull',
'Sorry, you do not have access rights to Digital fleet',
);
}
})
After the splash screen, and after the animated intro (see code below) , most of the time content appears immediately but sometimes randomly it takes up to 10-15s. After it does appear, any subsequent visit will work normal, for a while at least. I can't figure out if it's in-app bug or issue with server/database.
Here's a GIF illustrating the flow when it works properly:
Notice how list of Categories appear after "CHECK THIS OUT" Intro Image --- When the issue occurs, the Intro image fades-out, but the Categories don't appear for another 15s.
Using:
- Expo Go on iOS (only happens with expo publish, not in dev mode)
- Apollo and GraphQL to fetch content hosted on Heroku Postgres database.
- Heroku free dyno and Hobby-dev Postgres addon db (could this be the reason?)
If I minimize while this delay is ongoing, and come back to the app -- the Apollo useQuery hook will return an error.
if I don't minimize and just wait -- no error and it will eventually load.
It doesn't show the loading bar -- which should mean it's not stuck in "loading" state. Header and footer are visible - there's just empty screen where content should be.
HomeScreen.js:
import React, { useEffect, useState } from 'react'
import { View, Text, FlatList, Pressable, TouchableOpacity, ImageBackground, Image, Animated } from 'react-native'
import { useFocusEffect } from '#react-navigation/native';
import { gql, useQuery } from '#apollo/client'
import Bg from "../../../assets/background.png"
import Intro from "../../../assets/intro2x.png"
import { useFavContext } from '../../components/FavContext';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar';
import { LoadingLarge } from '../../components/Loading';
const HOME = gql`
query HomeData {
home {
data {
attributes {
...
}
}
}
}
`
const IntroMessageAnimation = (props) => {
const fadeAnimat = React.useRef(new Animated.Value(0)).current; // Initial value for opacity: 0
useFocusEffect(
React.useCallback(() => {
setTimeout(() => {
Animated.timing(fadeAnimat, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
}, 500);
setTimeout(() => {
Animated.timing(fadeAnimat, {
toValue: 0,
duration: 500,
useNativeDriver: true,
}).start();
}, 2000);
},[]))
return (
<Animated.View // Special animatable View
key="1ab"
style={{
flex: 1,
opacity: fadeAnimat, // Bind opacity to animated value
justifyContent: 'center',
alignItems: 'center'
}}>
{props.children}
</Animated.View>
);
}
const IntroMessage = () => (
<IntroMessageAnimation >
<Image
style={{
width: 273,
height: 324,
}}
source={Intro}
defaultSource={Intro}
resizeMethod='scale'
resizeMode='contain'
/>
</IntroMessageAnimation >
)
const MainContentAnimation = (props) => {
const fadeMainAnimat = React.useRef(new Animated.Value(0)).current; // Initial value for opacity: 0
useEffect(() => {
const timeout = setInterval(() => {
Animated.timing(fadeMainAnimat, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
}, 500);
return () => clearInterval(timeout)
})
return (
<Animated.View // Special animatable View
style={{
flex: 1,
opacity: fadeMainAnimat, // Bind opacity to animated value
}}>
{props.children}
</Animated.View>
);
}
export default ({ navigation }) => {
const insets = useSafeAreaInsets();
const { favorites } = useFavContext()
const { data, loading, error } = useQuery(HOME, {
notifyOnNetworkStatusChange: true,
fetchPolicy: 'network-only', // Used for first execution
nextFetchPolicy: 'cache-first', // Used for subsequent executions
})
const [isReady, setIsReady] = useState(false)
useEffect(() => {
const timer = setTimeout(() => {
setIsReady(true);
}, 2700);
return () => clearTimeout(timer);
}, []);
if ( !isReady || loading ) {
return (
<ImageBackground source={Bg} resizeMode="cover" style={tw.style('flex-1 relative')}>
<StatusBar style='light'/>
<IntroMessage/>
</ImageBackground>
)
} else if ( loading ) {
return <LoadingLarge />
}
if (error) {
return <Text>Error :( </Text>;
}
const heading = data.home.data.attributes.Heading;
const subheading = data.home.data.attributes.Subheading;
const categories = data.home.data.attributes.categories
const MainScreen = () => (
<MainContentAnimation >
<View
style={{
// ... inset paddings ...
}}>
<StatusBar style='light'/>
<View>
<Text>{heading}</Text>
<Text>{subheading}</Text>
</View>
<View>
<FlatList
showsVerticalScrollIndicator={false}
data={categories.data}
renderItem={({ item }) => (
<CategoryItem
favorites={favorites}
navigation={navigation}
category={item}
onPress={() =>
navigation.navigate('CategoryScreen', { item: item })}
/>
)}
keyExtractor={(item) => item.id.toString()}
/>
</View>
</View>
</MainContentAnimation >
)
return (
<ImageBackground source={Bg} resizeMode="cover" style={tw.style('flex-1 relative')}>
<MainScreen/>
</ImageBackground>
)
}
If relevant, App.js:
// Initialize Apollo Client
const client = new ApolloClient({
uri: 'https://janazaapp.herokuapp.com/graphql/',
cache: new InMemoryCache({
typePolicies: {
Subsubcategory: {
merge: true
},
},
})
});
function HomeStackScreen() {
return (
<HomeStack.Navigator
initialRouteName="HomeScreen"
screenOptions={({ navigation }) => {
return {
detachPreviousScreen: !navigation.isFocused(),
headerShown: true,
headerMode: 'float',
}
}}
>
<HomeStack.Screen
name="HomeScreen"
component={FadeHomeScreen}
options={({ navigation }) => ({
headerTitle: ({props}) => <LogoTitle onPress={() => navigation.navigate('HomeScreen')} />,
})}
/>
// ... other screrens ...
</HomeStack.Navigator>
);
}
function MainScreen() {
const ref = React.useRef(null);
const newFavor = JSON.stringify(favor.current)
return (
<SafeAreaProvider>
<ApolloProvider client={client}>
<NavigationContainer theme={MyTheme} ref={ref}>
<FavProvider favor={newFavor}>
<Tab.Navigator
screenOptions={({ route, navigation }) => ({
// ... screen options ...
})}
>
<Tab.Screen
name="Home"
component={HomeStackScreen}
options={{ tabBarLabel: 'Home', headerShown: false }}
/>
<Tab.Screen
name="Favourites"
component={FavouritesStackScreen}
options={{ tabBarLabel: 'Favourites', headerShown: true }}
/>
<Tab.Screen
name="Faq"
component={FaqStackScreen}
options={{
tabBarLabel: 'FAQs',
headerShown: true,
}}
/>
<Tab.Screen
name="Search"
component={SearchStackScreen}
/>
</Tab.Navigator>
</FavProvider>
</NavigationContainer>
</ApolloProvider>
</SafeAreaProvider>
);
}
}
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>
);
}
I was trying to use my new created navigator stack on my bottom navigator tabs item so that I can use it instead of a regular screen.
So far I created the new stack navigator:
const FavoritesNav = () => {
return(
<FavoritesStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: Colors.primaryColor,
},
headerTintColor: '#fff',
headerTitleStyle: {
fontSize: 17
}
}}>
<FavoritesStack.screen
name="Favorite"
component={FavoritesScreen}
/>
<FavoritesStack.screen
name="MealDetail"
component={MealDetailScreen}
/>
</FavoritesStack.Navigator>
);
};
And then, I tried to pass this on one of the item of my bottom navigator:
<MealsFavTabNavigator.Screen
name="Favorites"
component={FavoritesNav}
options={{
tabBarIcon: ({ focused, color, size }) => (
<Ionicons name="ios-star" size={25} color={focused ? "tomato" : "black"} />
)
}}
/>
This throws me an error: A navigator can only contain 'Screen' components as its diection children (found [object, object])
How can I pass my newly created stack to the bottom tab nav?
PS. Here's my complete code:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { Ionicons } from '#expo/vector-icons';
import { Platform } from 'react-native';
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs';
import CategoriesScreen from '../screens/CategoriesScreen';
import CategoryMealsScreen from '../screens/CategoryMealsScreen';
import MealDetailScreen from '../screens/MealDetailScreen';
import FavoritesScreen from '../screens/FavoritesScreen';
import HeaderButton from '../components/HeaderButton';
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { CATEGORIES } from '../data/dummy-data';
import Colors from '../constants/colors';
const MealsNav = createStackNavigator();
const MealsNavigator = () => {
return (
<MealsNav.Navigator
mode="modal"
screenOptions={{
headerStyle: {
backgroundColor: Colors.primaryColor,
},
headerTintColor: '#fff',
headerTitleStyle: {
fontSize: 17
}
}}
>
<MealsNav.Screen
name="Categories"
component={CategoriesScreen}
options={{
title: 'Meals Categories'
}}
/>
<MealsNav.Screen
name="CategoryMeals"
component={CategoryMealsScreen}
options={({ route }) => {
const catId = route.params.categoryId;
const selectedCategory = CATEGORIES.find((cat) => cat.id === catId);
return {
title: selectedCategory.title,
};
}}
/>
<MealsNav.Screen
name="MealDetail"
component={MealDetailScreen}
options={{
title: 'Meal Detail',
headerRight: () => (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Favorite'
iconName='ios-star'
onPress={() => console.log('Mark as the favorite')}
/>
</HeaderButtons>
),
}}
/>
</MealsNav.Navigator>
);
};
const MealsFavTabNavigator =
Platform.OS === 'android'
? createMaterialBottomTabNavigator()
: createBottomTabNavigator();
const getNavigationOptions = () => {
if (Platform.OS === 'ios') {
// Props for the ios navigator
return {
labeled: false,
initialRouteName: 'Meals',
tabBarOptions: {
activeTintColor: 'tomato',
inactiveTintColor: 'black',
},
};
}
// Props for android
return {
initialRouteName: 'Favorites',
activeColor: 'tomato',
inactiveColor: 'black',
barStyle: { backgroundColor: Colors.primaryColor },
shifting: true
};
};
const MealsTabNav = () => {
return (
<NavigationContainer>
<MealsFavTabNavigator.Navigator {...getNavigationOptions()} >
<MealsFavTabNavigator.Screen
name="Meals"
component={MealsNavigator}
options={{
tabBarIcon: ({ focused, color, size }) => (
<Ionicons name="ios-restaurant" size={25} color={focused ? "tomato" : "black"} />
)
}}
/>
<MealsFavTabNavigator.Screen
name="Favorites"
component={FavoritesNav}
options={{
tabBarIcon: ({ focused, color, size }) => (
<Ionicons name="ios-star" size={25} color={focused ? "tomato" : "black"} />
)
}}
/>
</MealsFavTabNavigator.Navigator>
</NavigationContainer>
);
};
const FavoritesStack = createStackNavigator();
const FavoritesNav = () => {
return(
<FavoritesStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: Colors.primaryColor,
},
headerTintColor: '#fff',
headerTitleStyle: {
fontSize: 17
}
}}>
<FavoritesStack.screen
name="Favorite"
component={FavoritesScreen}
/>
<FavoritesStack.screen
name="MealDetail"
component={MealDetailScreen}
/>
</FavoritesStack.Navigator>
);
};
export default MealsTabNav;
Thanks for those who will help.
Guess you are importing FavoritesScreen but using FavoriteScreen in code.
import FavoritesScreen from '../screens/FavoritesScreen';
const FavoritesNav = () => {
<NavigationContainer>
<FavoritesStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: Colors.primaryColor,
},
headerTintColor: '#fff',
headerTitleStyle: {
fontSize: 17
}
}}>
<FavoritesStack.screen
name="Favorite"
component={FavoritesScreen}
/>
<FavoritesStack.screen
name="MealDetail"
component={MealDetailScreen}
/>
</FavoritesStack.Navigator>
</NavigationContainer>
};
On my navigation tab, I want to change the color of the label + the color of the icon when its active, so what I did is created an if and else statement:
<MealsFavTabNavigator.Screen
name="Favorites"
component={FavoritesScreen}
options={({ route }) => ({
tabBarIcon: (tabInfo) => {
if (route.name === 'Favorites'){
return <Ionicons name="ios-star" size={25} color='tomato'/>
} else {
return <Ionicons name="ios-star" size={25} color='black' />
}
}
})}
My label color is also fixed above the Navigator level:
<MealsFavTabNavigator.Navigator
tabBarOptions={{
activeTintColor: Colors.primaryColor,
inactiveTintColor: 'black',
}}>
Here's my full code:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { Ionicons } from '#expo/vector-icons';
import CategoriesScreen from '../screens/CategoriesScreen';
import CategoryMealsScreen from '../screens/CategoryMealsScreen';
import MealDetailScreen from '../screens/MealDetailScreen';
import FavoritesScreen from '../screens/FavoritesScreen';
import HeaderButton from '../components/HeaderButton';
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { CATEGORIES } from '../data/dummy-data';
import Colors from '../constants/colors';
const MealsNav = createStackNavigator();
const MealsNavigator = () => {
return (
<MealsNav.Navigator
mode="modal"
screenOptions={{
headerStyle: {
backgroundColor: Colors.primaryColor,
},
headerTintColor: '#fff',
headerTitleStyle: {
fontSize: 17
}
}}
>
<MealsNav.Screen
name="Categories"
component={CategoriesScreen}
options={{
title: 'Meals Categories'
}}
/>
<MealsNav.Screen
name="CategoryMeals"
component={CategoryMealsScreen}
options={({ route }) => {
const catId = route.params.categoryId;
const selectedCategory = CATEGORIES.find((cat) => cat.id === catId);
return {
title: selectedCategory.title,
};
}}
/>
<MealsNav.Screen
name="MealDetail"
component={MealDetailScreen}
options={{
title: 'Meal Detail',
headerRight: () => (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Favorite'
iconName='ios-star'
onPress={() => console.log('Mark as the favorite')}
/>
</HeaderButtons>
),
}}
/>
</MealsNav.Navigator>
);
};
const MealsFavTabNavigator = createBottomTabNavigator();
const MealsTabNav = () => {
return (
<NavigationContainer>
<MealsFavTabNavigator.Navigator
tabBarOptions={{
activeTintColor: Colors.primaryColor,
inactiveTintColor: 'black',
}}>
<MealsFavTabNavigator.Screen
name="Meals"
component={MealsNavigator}
options={({ route }) => ({
tabBarIcon: ({ color }) => {
if(route.name === 'Meals'){
color = 'tomato'
} else if (route.name === 'Favorites') {
color = 'black'
}
return <Ionicons name="ios-restaurant" size={25} color={color}/>
}
})}
/>
<MealsFavTabNavigator.Screen
name="Favorites"
component={FavoritesScreen}
options={({ route }) => ({
tabBarIcon: (tabInfo) => {
if (route.name === 'Favorites'){
return <Ionicons name="ios-star" size={25} color='tomato'/>
} else {
return <Ionicons name="ios-star" size={25} color='black' />
}
}
})}
/>
</MealsFavTabNavigator.Navigator>
</NavigationContainer>
);
};
export default MealsTabNav;
How can I change the color of the label and color of the iconicons when its active? My solution doesnt work.
By default there are parameters for colors in tabbar icon and tabbar label and these are set to the active tint color and inactive tint color.
But if you have a requirement to override this you can do the following
<Tab.Screen
name="Feed"
component={Feed}
options={{
tabBarLabel:({ focused,color })=>(<Text style={{color:focused?"red":"aqua"}}>1232</Text>),
tabBarIcon: ({ focused,color, size }) => (
<MaterialCommunityIcons name="home" color={focused?"green":"blue"} size={size} />
),
}}
/>
References on the props
tabBarLabel can be a text or a react node, and you get the focused and the color as arguments, The color will be the color you set as activetintcolor or inactivetintcolor.
focused is a boolean on whether the tab is focused or not.
Same arguments are passed to the tabBarIcon only difference is the size which is the size of the icon.
If you see the code above, I have given custom colors based on focused without using the color that is passed. You can do this as per your requirement.
https://stackoverflow.com/a/63033684/17735463 - This is a valid answer but mine works fine too (react-navigation-v5)
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused }) => {
let iconName;
let size=25;
let color = focused ? 'black' : 'gray';
if (route.name === 'HomeScreen') {
iconName = 'ios-home';
return <Ionicons name={iconName} size={size} color={color} />;
} else if (route.name === 'FavoritesScreen') {
iconName = 'star';
return <AntDesign name={iconName} size={size} color={color} />;
}
tabBarLabel: ({ focused }) => {
let titleStyle = {
fontSize: 12,
fontWeight: focused ? 'bold' : '500',
color: focused ? 'black' : 'gray',
};
if (route.name === 'HomeScreen') {
return <Text style={titleStyle}>Home</Text>;
} else if (route.name === 'FavoritesScreen') {
return <Text style={titleStyle}>Favorites</Text>;
}
},
})}>
<Tab.Screen name='Home' component={HomeScreen}/>
<Tab.Screen name='Favorites' component={FavoritesScreen}/>
</Tab.Navigator>