I'm trying to make my own custom tab bar and it seems tabBarComponent is the way to do it by setting as my own component. With the below code my tab bar does not show up.
const TabNav = TabNavigator({
LaunchScreen: {
screen: PrimaryNav,
navigationOptions: {
tabBarLabel:'Find',
tabBarIcon: ({ tintColor }) => (
<Icon name='search' size={20} color='white' />
),
},
},
}, {
navigationOptions: {
headerTintColor: 'grey'
},
tabBarComponent: FooterTabs,
tabBarPosition: 'bottom',
swipeEnabled:false,
animationEnabled:false,
lazy:true,
tabBarOptions: {
showIcon:true,
showLabel:false,
style: {
backgroundColor: 'black'
}
}
});
In FooterTabs.js:
export default class FooterTabs extends Component {
render () {
return (
<View style={styles.container}>
<Text>FooterTabs Component</Text>
</View>
)
}
}
What am I missing?
const TabNav = TabNavigator({
......,
tabBarComponent: props => (
<FooterTabs{...props} />
),
tabBarPosition: 'bottom',
........
});
Try that. enclose your FooterTabs i.e <FooterTabs /> not FooterTabs
After some trial and error, the solution to my issue was to wrap my footer content in a ScrollView, then the tabs showed up as expected, though I am not sure why that is required.
I also implemented Caleb's suggestion (thanks!) of using tabBarComponent: props => <FooterTabs{...props} /> in order to pass the props which I need though was not the cause of the issue.
Related
StackOverflow I am very new to react native since I implement drawer navigation now I want to include a logout button at the end of the drawer but I don't find how to do that any good practice and ideas about how to achieve this kind of functionality. this is my code for drawer I find it from hours of google and it works fine but it has functions of screens I don't find any option of how to make a logout link in this code if this code is not correct then suggest any other good snippet thanks in advance
import React from 'react';
import { Ionicons } from '#expo/vector-icons'
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import { createDrawerNavigator, createStackNavigator, createAppContainer } from 'react-navigation';
import { DrawerActions } from 'react-navigation-drawer';
// _signOutAsync = async () => {
// await AsyncStorage.clear();
// this.props.navigation.navigate('Auth');
// };
const HomeScreen = () => (
<View style={styles.container}>
<Text>Home Screen!</Text>
</View>
);
const ProfileScreen = () => (
<View style={styles.container}>
<Text>Profile Screen!</Text>
</View>
);
const SettingsScreen = () => (
<View style={styles.container}>
<Text>Settings Screen!</Text>
</View>
);
const DrawerNavigator = createDrawerNavigator({
Home: {
screen: HomeScreen,
navigationOptions: ({ navigation }) => ({
title: 'Home Screen',
drawerLabel: 'Home',
drawerIcon: () => (
<Ionicons name="ios-home" size={20} />
)
})
},
Profile: {
screen: ProfileScreen,
navigationOptions: ({ navigation }) => ({
title: 'Profile Screen',
drawerLabel: 'Profile',
drawerIcon: () => (
<Ionicons name="ios-person" size={20} />
)
})
},
Settings: {
screen: SettingsScreen,
navigationOptions: ({ navigation }) => ({
drawerIcon: () => (
<Ionicons name="ios-settings" size={20} />
)
})
},
});
const StackNavigator = createStackNavigator({
DrawerNavigator: {
screen: DrawerNavigator,
navigationOptions: ({ navigation }) => {
const { state } = navigation;
if(state.isDrawerOpen) {
return {
headerLeft: ({titleStyle}) => (
<TouchableOpacity onPress={() => {navigation.dispatch(DrawerActions.toggleDrawer())}}>
<Ionicons name="ios-close" style={styles.menuClose} size={36} color={titleStyle} />
</TouchableOpacity>
),
}
}
else {
return {
headerLeft: ({titleStyle}) => (
<TouchableOpacity onPress={() => {navigation.dispatch(DrawerActions.toggleDrawer())}}>
<Ionicons name="ios-menu" style={styles.menuOpen} size={32} color={titleStyle} />
</TouchableOpacity>
),
}
}
}
}
})
export default createAppContainer(StackNavigator);
You can create one content component to render inside your Drawer Navigator, making it easier to modify.
I will explain with your example (I am assuming that it is your App.js file):
//import CustomDrawer from '...'
const DrawerNavigator = createDrawerNavigator({
Home: {
screen: HomeScreen,
navigationOptions: ({ navigation }) => ({
title: 'Home Screen',
drawerLabel: 'Home',
drawerIcon: () => (
<Ionicons name="ios-home" size={20} />
)
})
},
Profile: {
screen: ProfileScreen,
navigationOptions: ({ navigation }) => ({
title: 'Profile Screen',
drawerLabel: 'Profile',
drawerIcon: () => (
<Ionicons name="ios-person" size={20} />
)
})
},
Settings: {
screen: SettingsScreen,
navigationOptions: ({ navigation }) => ({
drawerIcon: () => (
<Ionicons name="ios-settings" size={20} />
)
})
},
//Here you will add one more pair of curly brackets to add more configs.
}, {
initialRouteName: 'Home',
contentComponent: CustomDrawer //This is the option that will allow you to add the button
});
You will create one entire component to modify like you want and then render inside the Drawer Navigator. Remember, I am using CustomDrawer name, you will import this component inside your App.js file.
import React from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
import { Button } from 'react-native-elements';
import { DrawerNavigatorItems } from 'react-navigation-drawer';
const CustomDrawer = ({ ...props }) => {
return (
<>
<View>
<DrawerNavigatorItems
{...props}
itemsContainerStyle={{}}
itemStyle={{}}
/>
</View>
<View
style={{
flexDirection: 'row',
alignSelf: 'center',
position: 'relative',
marginBottom: 20,
}}
>
<Button
title={'Log out'}
buttonStyle={{ width: 200, borderRadius: 20 }}
onPress={}
/>
</View>
</>
);
};
const styles = StyleSheet.create({});
export default CustomDrawer;
Here I am rendering only the CustomDrawer props, that is the itens that you create in your App.js and rendering it (specifically it is the ...props that I am passing in DrawerNavigationItems, so you can customize it like you want, like one normal screen, place buttons, create views and apply styles to it.
You can also instead of creating one new screen to render inside your Drawer Navigator code it inside your App.js, but personally I feel it much messed up
You can learn more with this tutorial
We are trying to update the badge count in a react-navigation BottomTabBar every time we update the store values. It is successfully updated when we update the cart when going from one page to the next, but if we try to update the cart on that same page, the badge is not changed, but as soon as we click on another tab, the value is changed to the correct up-to-date value. Is there a way to have this value change automatically as soon as the store is updated? Since the router is not a class component, we are unable to wrap it with a mobx observer.
This is where we declare our stack navigator for the tabs in router.js:
export const Tabs = createBottomTabNavigator({
'Home': {
screen: Home,
navigationOptions: {
tabBarLabel: 'Home',
tabBarIcon: ({tintColor}) => (<View><Icon name="home" color={tintColor} type="light" size={22}/></View>),
header: null,
},
},
'Items': {
screen: MenuNav,
navigationOptions: {
tabBarLabel: 'Menu',
tabBarIcon: ({tintColor}) => (<View><Icon name="utensils" color={tintColor} type="light" size={22}/><View></View></View>),
},
},
'Cart': {
screen: Checkout,
navigationOptions: {
tabBarLabel: 'My Order',
tabBarIcon: ({tintColor}) => (
<View>{store.draft.quantity ?
<View>
<View style={{position: 'absolute', top: -10, left: -10, backgroundColor: store.theme.primary_button_color, width: 20, height: 20, borderRadius: 50, zIndex: 100,}}>
<Text style={{ color: store.theme.primary_button_text_color, position: 'relative', left: 7, top: 4, fontSize: 10}}>{store.draft.quantity}</Text>
</View>
<Icon name="shopping-bag" color={tintColor} type="light" size={22}/>
</View> : <View><Icon name="shopping-bag" color={tintColor} type="light" size={22}/></View>}
</View>),
},
},
'Info': {
screen: Info,
navigationOptions: {
tabBarLabel: 'Restaurant',
tabBarIcon: ({tintColor}) => (<View><Icon name="map-marker-smile" color={tintColor} type="light" size={22}/><View></View></View>),
},
}
},
{tabBarComponent: (props) => {
return (
<TabBar
{...props}
/>
);
},
tabBarPosition: 'bottom',
},
);
This is how we are rendering our tabs:
import React, { Component } from 'react';
import { BottomTabBar } from 'react-navigation-tabs';
import { withNavigation } from 'react-navigation';
import { observer } from 'mobx-react';
import globalScss from "../styles.scss";
class TabBar extends Component {
render() {
return (
<BottomTabBar
{...this.props}
activeTintColor={'#898989'}
inactiveTintColor={'#FFF'}
style={[{ height: 60, paddingTop: 7 }, globalScss.primary_color]}
/>
);
}
}
export default withNavigation(observer(TabBar));
I ended up using navigation params instead of mobx state.
defaultNavigationOptions: ({navigation}) => ({
tabBarIcon: () => {
rootStore.setNavigation(navigation);
const {routeName} = navigation.state;
if (routeName === 'Tab') {
const badgeCount = navigation.getParam('badgeCount',0)
return (
<View style={{flexDirection: 'row',alignItems: 'center',justifyContent: 'center'}}>
<IconBadge
MainElement={
<View style={{
width:30,
height:30,
margin:6
}}
>
<Image
source={require('../../assets/Icons/tab_icon-01.png')}
style={{width: '100%', height: '100%'}}/>
</View>
}
BadgeElement={
<Text style={{color:'#FFFFFF'}}>{badgeCount}</Text>
}
IconBadgeStyle={
{
width:20,
height:20,
backgroundColor: '#F20779',
}}
Hidden={badgeCount === 0}
/>
</View>
);
} },
}),
i set the navigation with in the mobx store :
setNavigation(navigation) {
this.navigation = navigation
}
then I update the navigation param with :
setBadgeValue =() => {
if (this.navigation !== null)
this.navigation.setParams({badgeCount: this.pendingItems.length});
};
You can also update the badge in the Notification tab using DeviceEventEmitter in React Native... You just need to emit when notification tapped and then addListener in Badge component class to update count in the badge.
In the notification class:
DeviceEventEmitter.emit('updateBadgeCount', {count:value})
In Badge component class:
DeviceEventEmitter.addListener('updateBadgeCount', this.updateBadgeCount.bind(this))
updateBadgeCount(c) {
notificationCount = c.count
this.forceUpdate()
}
You can also update badge in Notification tab using DeviceEventEmitter in React Native.. You just need to emit when notification tapped and then addListener in Badge component class to update count in the badge.
I am following the React-Navigation tutorial, and got stuck on the section titled Header interaction with its screen component. The code in the tutorial work fine in the emulator provided at snack, but I discovered that when running locally I encountered the following error:
Warning: Failed prop type: The prop 'onPress' is marked as required in 'Button', but its value is 'undefined'.
I managed to get the code working on my local machine using expo-cli by changing the onPress event assignment in navigationOptions as follows (my snack here):
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')} - as in tutorial
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
I am hoping someone might have some insight into why this is so. I checked and I am using the same version of Expo (v.32.0) locally.
App.js listing:
import React from 'react';
import { Button, Image, Platform, View, Text } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
class LogoTitle extends React.Component {
render() {
return (
<Image
source={require('./spiro.png')}
style={{ width: 30, height: 30 }}
/>
);
}
}
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')}
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
),
};
};
componentWillMount() {
this.props.navigation.setParams({ increaseCount: this._increaseCount });
}
state = {
count: 0,
};
_increaseCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Text>Count: {this.state.count}</Text>
<Button
title="Go to Details"
onPress={() => {
/* 1. Navigate to the Details route with params */
this.props.navigation.navigate('Details', {
itemId: 86,
otherParam: 'First Details',
});
}}
/>
</View>
);
}
}
class DetailsScreen extends React.Component {
static navigationOptions = ({ navigation, navigationOptions }) => {
const { params } = navigation.state;
return {
title: params ? params.otherParam : 'A Nested Details Screen',
/* These values are used instead of the shared configuration! */
headerStyle: {
backgroundColor: navigationOptions.headerTintColor,
},
headerTintColor: navigationOptions.headerStyle.backgroundColor,
};
};
render() {
/* 2. Read the params from the navigation state */
const { params } = this.props.navigation.state;
const itemId = params ? params.itemId : null;
const otherParam = params ? params.otherParam : null;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Text>otherParam: {JSON.stringify(otherParam)}</Text>
<Button
title="Update the title"
onPress={() =>
this.props.navigation.setParams({ otherParam: 'Updated!' })
}
/>
<Button
title="Go to Details... again"
onPress={() => this.props.navigation.navigate('Details')}
/>
<Button
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
const RootStack = createStackNavigator(
{
Home: {
screen: HomeScreen,
},
Details: {
screen: DetailsScreen,
},
},
{
initialRouteName: 'Home',
defaultNavigationOptions: {
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
}
);
const AppContainer = createAppContainer(RootStack);
export default class App extends React.Component {
render() {
return <AppContainer />;
}
}
My guess is that this is not a fatal error, just a warning.
It will happen in any case. React Navigation docs state:
React Navigation doesn't guarantee that your screen component will be mounted before the header. Because the increaseCount param is set in componentDidMount, we may not have it available to us in navigationOptions. This usually will not be a problem because onPress for Button and Touchable components will do nothing if the callback is null. If you have your own custom component here, you should make sure it behaves as expected with null for its press handler prop.
So, navigationOptions function will be called twice:
First time before componentDidMount. Here, getParam will return undefined.
Second time after componentDidMount.
What Button is complaining about, is the first time. It does not like onPress set to undefined.
You can check this with console.log from navigationOptions:
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
console.log(navigation.getParam('increaseCount'))
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')}
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
),
};
};
In my opinion, your code is correct, while the code from the docs simply ignores this issue.
Try instead of navigation.getParam() to use navigation.navigate()
I'm experiencing a display issue with React Native / React Navigation where navigating to a different tab within a Tab Navigator causes a bad bounce animation to trigger when the tab opens. This issue only happens after logging in, and after this happens once or twice it doesn't happen again.
Below is a 8 second video clip of the issue:
Youtube clip of error
What I've tried so far:
InteractionManager.runAfterInteractions within componentDidMount() to prevent fetching data during navigation animation
Turning lazyLoad on & off within the TabNavigator
Forcing the mapview to reload with this.forceUpdate() before navigating to another tab
Unfortunately none of this has worked, and I'm not sure where the problem is coming from now.
What I'm running:
"react-navigation": "^1.5.11"
"expo": "^26.0.0"
Relevant code snippet of React Navigation setup (the clip shows navigating from the userInfo > map > yourEvents:
export default class App extends React.Component {
render() {
const Stack = {
FirstView: {
screen: TabNavigator(
{
map: {
screen: HomeMapScreen,
transitionConfig: () => fromLeft(),
navigationOptions: ({ navigation }) => ({
header: null
})
},
yourEvents: {
screen: YourEventsScreen,
transitionConfig: () => fromLeft(),
navigationOptions: ({ navigation }) => ({
header: null
})
},
...
},
{
navigationOptions: {
animationEnabled: "false"
},
tabBarPosition: "bottom",
animationEnabled: false,
swipeEnabled: false,
tabBarOptions: {
showIcon: "true", // Shows an icon for both iOS and Android
style: {
backgroundColor: "#04151F"
},
showLabel: false,
activeTintColor: "#59C9A5",
inactiveTintColor: "#F7FFF7",
labelStyle: {
fontSize: 10
},
iconSize: Platform.OS === "ios" ? 30 : 24
}
}
)
},
...
userInfo: {
screen: UserInfo,
transitionConfig: () => fromLeft(),
navigationOptions: {
drawerLabel: <Hidden />
}
},
...
};
const DrawerRoutes = {
...
Home: {
name: "Home",
screen: StackNavigator(Stack, {
initialRouteName: "FirstView",
transitionConfig: () => fromLeft(),
headerMode: "none",
drawerIcon: () => {
return <Icon name="map" type="foundation" size={30} color={tintColor} />;
}
})
},
SecondViewStack: {
name: "SecondViewStack",
screen: StackNavigator(Stack, {
initialRouteName: "SecondView",
transitionConfig: () => fromLeft(),
icon: () => {
return <Icon name="map" type="foundation" size={30} color={tintColor} />;
}
})
}
};
const RootNavigator = StackNavigator(
{
Drawer: {
name: "Drawer",
screen: DrawerNavigator(DrawerRoutes, {
drawerBackgroundColor: "#D8DDEF",
transitionConfig: () => fromLeft(),
contentComponent: DrawerContent,
contentOptions: {
backgroundColor: 'black',
flex: 1
},
})
},
...Stack
},
{
headerMode: "none"
}
);
return (
<Provider store={store}>
<View style={styles.container}>
<MyStatusBar translucent backgroundColor="#04151F" barStyle="light-content" />
<RootNavigator />
</View>
</Provider>
);
}
}
Snippet of component displaying the "bounce" issue when loaded:
class YourEventsScreen extends Component {
state = {
attendingEvents: true,
ownedEvents: false,
isLogoutVisible: false,
animatePressAttend: new Animated.Value(1),
animatePressHost: new Animated.Value(1),
didFinishInitialAnimation: false,
}
static navigationOptions = {
title: "Your Events",
headerLeft: null,
tabBarIcon: ({ tintColor }) => {
return <Icon name="calendar" size={30} color={tintColor} type="entypo"/>;
}
};
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.props.fetchOwnedEvents(this.props.currentUser.userId);
this.props.fetchAttendingEvents(this.props.currentUser.attendingEvents);
this.setState({
...this.state,
didFinishInitialAnimation: true,
});
});
}
Any ideas or insight on the issue here is very much appreciated!
I can only imagine what is happening here is that you are using LayoutAnimation somewhere when this tab change takes place. A good strategy when encountering issues like this is to remove code until the issue is resolved, then add it back piece by piece.
I had a similar issue when I toggled the visibility of a MapView by updating the state with react-navigation's onDidFocus and onWillBlur functions (from NavigationEvents).
Once I stopped updating this and removed my showMap parameter, the bouncing effect went away.
I have a StackNavigator, where I've specified the same headerRight Icon for every screen:
export default StackNavigator(
{
Authorization: {
screen: AuthorizationScreen
},
SignIn: {
screen: SignInScreen
},
SignUp: {
screen: SignUpScreen
},
Main: {
screen: MainScreen
},
Language: {
screen: LanguageScreen
},
//...etc
},
{
navigationOptions: {
headerRight: (
<Icon color={'#77767c'}
name='ios-contact-outline'
size={30}
style={{ paddingRight: 30}}
type='ionicon'
/>
),
}
}
)
All the screens are imported from separate files. When this Icon is pressed, I want the same component to render regardless of what screen I'm on. The problem is, I can't think of a way to do this outside of writing in some kind of state handling and onPress function for every single screen I have, which would be really tedious to write and maintain. Is there any way to get around this and only write the component rendering once?
You can create one component for Header and pass it into all the screen's navigationOptions. Then you just need to handle the method on each screen and you can do your stuff at here.
Custom Header Class:
class Header extends Component {
render() {
const props = this.props;
return (<View>
<View
style={KEEP_YOUR_STYLE}
/>
<View style={styles.containerStyle} >
<Text
style={your_style}
numberOfLines={1}
>TITLE</Text>
<TouchableOpacity
style={styles.touchableOpacityStyle}
onPress={props.onPress}
>
<Image
source={YOUR_ICON}
style={{
position:'absolute',
right: 10,
width: 20,
height: 20,
resizeMode: 'cover',
}
} />}
</TouchableOpacity>
</View>
</View>
);
}
}
export { Header };
In your StackNavigator:
const defaultNavigation = ({ navigation }) => ({
header: (<Header
title='Hellow'
/>),
});
Language: {
screen: LanguageScreen,
navigationOptions: defaultNavigation,
},
In your particular Screen:
static navigationOptions = ({ navigation }) => ({
header: (
<Header
title='Your Title'
onPress={() => {
// DO YOUR STUFF
}}
/>),
});