Is there a option to disable tabbar buttons - react-native

In my react native app, I have a router component which uses react-navigation-material-bottom-tabs.
In that component I have created it like this.
const tabNavigator = createMaterialBottomTabNavigator({
home: {
screen: Home,
navigationOptions: ({ navigation }) => ({
title: ''
})
},
createCampaign: {
screen: CreateCampaign,
navigationOptions: ({ navigation }) => ({
title: '',
tabBarVisible: false
})
},
settings: {
screen: AllSettings,
navigationOptions: ({ navigation }) => ({
title: ''
})
}
});
This is working fine. But I want to disable some tabs of this bar under some conditions. As example, if the profile hasn't been approved, disable the tab to settings. Is there any way to do this in my screens?(Better if it's not in the router because I can't send a API request in router). How can I access tabBar options in screens? How to disable tabs? Please help.

For Version 5.x there's a new way to do it.
<Tabs.Screen
name="Chat"
component={Chat}
listeners={{
tabPress: e => {
// Prevent default action
e.preventDefault();
},
}}
/>
Here's the reference link to the docs: https://reactnavigation.org/docs/navigation-events/

What are you using for global state management inside your app, please store the status weather profile is approved or not inside your global state. Then you can override tabBarOnPress to check if user is approved and perform the actions accordingly, code snippet below.
const Tab_Navigator = createBottomTabNavigator({
First:{
screen: First,
},
Second:{
screen: Second,
},
Third:{
screen: Third,
}
}, defaultNavigationOptions: ({ navigation }) => ({
tabBarOnPress: ({ navigation, defaultHandler }) => {
if (
navigation.state.routeName === "Second" ||
navigation.state.routeName === "Third"
) {
return null;
}
defaultHandler();
},})

You can try with tabBarComponent props available in the second parameter for createBottomTabNavigator.
You can enable or disable button as you like but please look at the note below.
I used native base Footer, you can use your preferred components.
import { Footer, FooterTab } from 'native-base'
const HomeScreen = createBottomTabNavigator(
{
First:{
screen: First,
},
Second:{
screen: Second,
},
Third:{
screen: Third,
}
},
{
tabBarComponent: props => {
return (
<Footer>
<FooterTab>
<Button
vertical
active={props.navigation.state.index === 0}
onPress={() => props.navigation.navigate(actions.First)}
>
</Button>
<Button
vertical
active={props.navigation.state.index === 1}
onPress={() => props.navigation.navigate(actions.Second)}
>
</Button>
<Button
vertical
active={props.navigation.state.index === 2}
onPress={() => props.navigation.navigate(actions.Third)}
>
</Button>
</FooterTab>
</Footer>
)
}
}
)
Note: If you want to dynamically change your buttons(tab bars elements) you can not do it as these buttons are assigned on the basis of the index.

I am Working on the old Project there used React-Navigation version 4.
The Solution that worked for me is follow ....
Home: {
screen: HomeStack,
navigationOptions: {
tabBarOnPress:()=>{
return null;
},
tabBarLabel: "Home",
tabBarOptions: {
activeTintColor: "#455BE0",
inactiveTintColor: "gray",
},
tabBarIcon: ({ tintColor }) => (
<Image
style={{ height: 25, width: 25, tintColor: tintColor, marginTop: 10 }}
source={require("./src/assets/home.png")}
/>
),
},
},
You can check in the navigationOptions added tabBarOnPress which return value is null.
This solution work for me Hope work for you also.

You can overwrite tabBarButton​ using Pressable or Button component and add a conditional in order to disable the button. Something like this :
<Stack.Screen
name="YourScreen"
options={{
tabBarButton: (props) => (
<Pressable
{...props}
isDisabled={conditional ? true : false}
/>
),
}}
component={YourComponent}
/>

Related

How can I render a Tab Item for React Navigation Material Bottom Tab based on store value?

I'm using React Navigation Material Bottom Tabs and I need to add an option based on a store value.
I have a Material Bottom Tab for Authenticated user, then I wanna get the user kind value from store and render a tab item based on value at my reducer.
I tried the below solution but I always get store initial state.
createMaterialBottomTabNavigator(
{
Events: {
screen: EventsStack,
navigationOptions: {
tabBarLabel: 'Agenda',
tabBarIcon: ({ tintColor }) => (
<MaterialCommunityIcons name="calendar" size={ICON_SIZE} color={tintColor} />
),
},
},
...((() => {
const userKind = store.getState() && store.getState().auth.user.kind;
return userKind === 'p';
})() && {
Consultations: {
screen: PatientsStack,
navigationOptions: {
tabBarLabel: 'Atendimentos',
tabBarIcon: ({ tintColor }) => (
<MaterialCommunityIcons name="doctor" size={ICON_SIZE} color={tintColor} />
),
},
},
}),
Patients: {
screen: PatientsStack,
navigationOptions: {
tabBarLabel: 'Pacientes',
tabBarIcon: ({ tintColor }) => (
<MaterialCommunityIcons name="account-multiple" size={ICON_SIZE} color={tintColor} />
),
},
},
Settings: {
screen: SettingsStack,
navigationOptions: {
tabBarLabel: 'Ajustes',
tabBarIcon: ({ tintColor }) => (
<MaterialCommunityIcons name="settings" size={ICON_SIZE} color={tintColor} />
),
},
},
},
{
initialRouteName: 'Events',
labeled: false,
shifting: false,
activeTintColor: Colors.dsBlue,
barStyle: {
borderTopWidth: 1,
borderTopColor: Colors.dsSky,
backgroundColor: Colors.colorWhite,
},
tabBarOptions: {
activeTintColor: Colors.dsBlue,
activeBackgroundColor: Colors.dsSkyLight,
inactiveTintColor: Colors.dsInkLighter,
inactiveBackgroundColor: Colors.dsSkyLight,
upperCaseLabel: false,
labelStyle: {
fontSize: 11,
},
style: {
backgroundColor: Colors.dsSkyLight,
},
showIcon: true,
pressColor: Colors.dsSkyLight,
indicatorStyle: {
backgroundColor: Colors.dsBlue,
},
},
},
)
I want to conditionally render Consultations tab item based on my store value.
Using store.getState() in the navigator will give you the initial store but as you have pointed out, it will not get called again when the store is updated, so the navigator will never change.
The solution to have the navigation updated when the state changes is to use a component that is connected to the Redux store.
Let's say you want to change the title of a tab depending on a value in your Redux store.
In that case, you would simply define a Label.js component like so:
import React from 'react';
import { connect } from 'react-redux';
import { Text } from 'react-native';
const Label = ({ user }) => {
if (!user) return 'Default label';
return <Text>{user.kind}</Text>;
};
function mapStateToProps(state) {
return { user: state.auth.user };
}
export default connect(mapStateToProps)(Label);
Here we assume that you have a reducer that updates the auth key in your store with the user object whenever it changes.
Now all you would have to do is import <Label /> in your navigation:
import Label from './Label';
export default createMaterialBottomTabNavigator({
Events: {
screen: EventsStack,
navigationOptions: {
tabBarLabel: <Label />,
},
},
});
VoilĂ ! Whenever you update auth.user.kind in your store, the tab navigation gets updated.
Now I am aware that in your case you want something a bit more complicated than updating the label depending on the store, you want to display or hide a tab dynamically.
Unfortunately react-navigation does not provide a hideTab option for a given tab yet. There is a tabBarVisible option but it only applies to the whole bar, not a single tab.
With the labels, we managed to do it by connecting a component to the store. But what component should we target here?
The workaround is to use the tabBarComponent option. It allows us to override the component to use as the tab bar. So we just have to override it with a component that is connected to the store and we have our dynamic tab bar!
Now our createMaterialBottomTabNavigator becomes:
import WrappingTabBar from './WrappingTabBar';
export default createMaterialBottomTabNavigator({
Events: {
screen: EventsStack,
navigationOptions: {
tabBarLabel: 'Agenda',
},
},
Consultations: {
screen: PatientsStack,
navigationOptions: {
tabBarLabel: 'Atendimentos',
},
},
}, {
tabBarComponent: props => <WrappingTabBar {...props} />, // Here we override the tab bar component
});
And we define <WrappingTabBar> in WrappingTabBar.js by rendering a basic BottomTabBar connected to the store and that filters out the routes in the navigation state that we do not want.
import React from 'react';
import { connect } from 'react-redux';
import { BottomTabBar } from 'react-navigation';
const TabBarComponent = props => <BottomTabBar {...props} />; // Default bottom tab bar
const WrappingTabBar = ({ navigation, user, ...otherProps }) => (
<TabBarComponent
{...otherProps}
navigation={{
...navigation,
state: {
...navigation.state,
routes: navigation.state.routes.filter(r => r.routeName !== 'Consultations' || (user && user.kind === 'p')), // We remove unwanted routes
},
}}
/>
);
function mapStateToProps(state) {
return { user: state.auth.user };
}
export default connect(mapStateToProps)(WrappingTabBar);
This time, whenever your auth reducer receives a new value for auth.user, it updates the store. Since WrappingTabBar is connected to the store, it renders again with a new value of auth.user.kind. If the value is "p", the route corresponding to the second screen gets removed from the navigation state and the tab gets hidden.
This is it! We end up with a tab navigation that can display tabs dynamically depending on values in our Redux store.

React native - onpress doesn't work inside drawer navigation header

I'm trying to add a custom menu icon button inside the navigation header in my react native app. I successfully did that, but the press event doesn't fire as long as the icon is in the header. For example, if I have the icon here:
https://www.dropbox.com/s/xyah9ei43wgt1ut/menu_regular.png?dl=0
The press event doesn't work, but if I have it here (moved it lower):
https://www.dropbox.com/s/54utpr1efb3o0lm/menu_moved.png?dl=0
The event fires ok.
Here's my current setup navigator:
const MainNavigator = createStackNavigator(
{
login: { screen: MainLoginScreen },
signup: { screen: SignupScreen },
profileScreen: { screen: ProfileScreen },
main: {
screen: createDrawerNavigator({
Home: createStackNavigator(
{
map: {
screen: MapScreen,
headerMode: 'screen',
navigationOptions: {
headerVisible: true,
headerTransparent: false,
headerLeft: (
<View style={{ position: 'absolute', left: 10, display: 'flex', zIndex: 11550 }}>
<Icon
raised
name='bars'
type='font-awesome'
color='rgba(255, 255, 255, 0)'
reverseColor='#444'
onPress={() => { console.log("press"); navigation.goBack() }}
reverse
/>
</View>
)
}
},
history: { screen: HistoryScreen },
foundItem: { screen: FoundItemScreen },
}
),
Settings: {
screen: SettingsScreen,
navigationOptions: ({ navigation }) => ({
title: 'Edit Profile',
})
}
}, {
contentComponent: customDrawerComponent,
drawerWidth: width * 0.8
}
)
}
}, {
headerMode: 'screen',
navigationOptions: {
headerTransparent: true,
headerLeftContainerStyle: { paddingLeft: 20 }
}
}
);
The icon from the screenshot is inside headerLeft.
I've also tried various zIndex values, but with no luck.
Thanks in advance!
Edit:
The drawer has the same issue on the first item, press events don't work on the full area of the drawer item when it's over the header:
https://www.dropbox.com/s/krva5cgp7s59d13/drawer_opened.png?dl=0
Try to wrap Icon inside some Touchable Element like TouchableOpacity/TouchableHighlight and then add an onPress prop to that element
You can get the onPress event in navigationOption's header using navigation params like
static navigationOptions = ({ navigation }) => ({
headerLeft: (
<TouchableOpacity onPress={() => navigation.state.params.handleSave()} >
<Image source={ImageSource} />
</TouchableOpacity>
)
})
Then set the navigation params from the componentDidMount like:
componentDidMount() {
this.props.navigation.setParams({ handleSave: this.onSavePress.bind(this)})
}
then can call the method like:
onSavePress() {
// Do Something
}

Tab Navigator On focussed

I have a TabNavigator
const Tabs = TabNavigator({
Test: {
screen: SCA,
navigationOptions: () => ({
title: 'Home',
tabBarIcon: ({ tintColor }) => {
return (
<FAIcon
name='screen1'
size={25}
color={tintColor}
/>
);
}
})
},
Screen2: {
screen: SCB,
navigationOptions: () => ({
title: 'screen2',
tabBarIcon: ({ tintColor }) => {
return (
<MIcon
name='account-circle'
size={25}
color={tintColor}
/>
);
}
})
},
screen3: {
screen: MYSCREEN,
navigationOptions: () => ({
title: 'dd',
tabBarIcon: ({ tintColor }) => {
return (
<MIcon
name='account-circle'
size={25}
color={tintColor}
/>
);
}
})
}
}, {
tabBarPosition: 'top',
tabBarOptions: {
showIcon: true,
showLabel: true,
inactiveTintColor: Colors.blue,
activeTintColor: Colors.redColor,
pressColor: Colors.redColor,
indicatorStyle: { backgroundColor: Colors.redColor },
style: {
backgroundColor: Colors.white
}
}
});
Basically I have tab navigator with 3 tabs. Whenever screen3 is focussed via tab button press or by swiping how can I get to know in MYSCREEN that this screen is focussed again.(Screen looks similar to playstore app with selecting screen on tab as well as swiping)
Class MYSCREEN extends React.Component{
//some function like on onfocus?
}
I tried searching it showed onFocusComponent will work but it didn't.
What should I do?
Since you are using react-navigation you can use listeners on the navigation lifecycle events. https://reactnavigation.org/docs/en/navigation-prop.html#addlistener-subscribe-to-updates-to-navigation-lifecycle
There are four events you can subscribe to:
willFocus - the screen will focus
didFocus - the screen focused (if there was a transition, the transition completed)
willBlur - the screen will be unfocused
didBlur - the screen unfocused (if there was a transition, the transition completed)
You can subscribe to as many of them as you want. Here is an example of using didFocus. You could easily replicate this for all that you require.
So in each of the screens of your TabNavigator, you can add the following:
componentDidMount () {
// add listener
this.didFocusSubscription = this.props.navigation.addListener('didFocus', this.didFocusAction);
}
componentWillUmount () {
// remove listener
this.didFocusSubscription.remove();
}
didFocusAction = () => {
// do things here when the screen focuses
}
You can use withNavigationFocus HOC which will add isFocused prop to your screen component's props then catch that prop in getDerivedStateFromProps method and check if isFocused is trueand then return a new state so that the component will render again.

Force scrollview to re-render when closing a react-navigation modal?

I'm opening a modal, nested in a react-navigation TabNavigator.
This modal is used to add, say a teammate, to a Realm database.
When the new teammate is saved, I close the modal and "navigate" back to a ScrollView displaying all the teammates, retrieved from my Realm database.
My problem is that this ScrollView doesn't re-render when closing the modal.
I am not using Redux or any other state manager.
This is the code executed when saving a user:
onButtonPress() {
var teammate = new TeammateModel(this.firstname, this.lastname);
if (this.firstname && this.lastname) {
TeammateService.save(teammate);
this.props.navigation.goBack(null);
}
}
And this is the code of the for the component rendering the ScrollView:
export default class Team extends Component {
componentWillMount() {
teammates = TeammateService.findAll();
}
render() {
return (
<ScrollView>
<List>{teammates.map((teammate) => (
<ListItem
key={teammate.id}
title={`${teammate.firstname} ${teammate.lastname}`}
/>
))}
</List>
</ScrollView>
);
}
}
As requested here's also my routes.js where the navigation happens:
export const TeamStack = StackNavigator({
Team: {
screen: Team,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Team',
headerRight: <Button title="Add" onPress={() => navigation.navigate('EditTeammate')} />
}),
},
})
export const EditTeammateStack = StackNavigator({
EditTeammate: {
screen: EditTeammate,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Teammate',
headerLeft: <Button title="Cancel" onPress={() => navigation.goBack(null)} />
}),
}
})
export const NoteStack = StackNavigator({
EditNote: {
screen: EditNote,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Edit note'
})
}
})
export const Tabs = TabNavigator({
EditNote: {
screen: NoteStack,
navigationOptions: ({ navigation }) => ({
header: { visible: true },
headerTitle: 'Edit note',
tabBarLabel: 'Notes',
tabBarIcon: ({ tintColor }) => <Icon name="list" size={20} color={tintColor} />
}),
},
Team: {
screen: TeamStack,
navigationOptions: ({ navigation }) => ({
header: { visible: true },
headerTitle: 'Team',
tabBarLabel: 'Team',
tabBarIcon: ({ tintColor }) => <Icon name="users" size={20} color={tintColor} />
}),
}
}, {
initialRouteName: 'Team',
});
export const Root = StackNavigator({
Tabs: {
screen: Tabs,
},
EditTeammate: {
screen: EditTeammateStack,
},
}, {
mode: 'modal',
headerMode: 'none'
});
export default Root;
I'm sure I'm doing one (or more) thing(s) wrong. But what? :)

React-Navigation Drawer and Static Tab-Navigation

i am switching from react navigator to react-navigation and i am actually fine with this approach, but struggeling with one issue.
I want to use a drawer navigation and a bottom aligned Tab-Navigation.
This part is working as expected - no issues here.
I want to have the tabbed navigation fixed with 3 Buttons that will have the same action all over the app. ( eg. dashboard / search / favourites )
From Dashboard you can navigate one level deeper. As i am doing it now, the Label of the Tab formerly "Dashboard" changes to the Name of the navigated-to Item.
to clarify, i added a stack-navigation in the Dashboard-Screen-Tab, so the user can navigate through that pages.
How can i prevent the tabs' labes and actions to change while navigating within the tabs' stack?
Basically i want a fixed Tab Navigation on each screen.
Should i create a fixed View-Component to achieve that?
Here is my setup:
App.js
const MetaTabNavigator = TabNavigator({
Dashboard: {
screen: MachineNavigator
},
Search: { screen: SearchScreen },
Favourites: { screen: FavouritesScreen },
},
{
tabBarPosition: Platform.OS === "ios" ? 'bottom' : 'top',
animationEnabled: true,
tabBarOptions: {
activeTintColor: STYLES.HIGHLIGHT_COLOR
},
swipeEnabled: false,
backBehavior: 'none'
});
const MetaDrawerNavigator = DrawerNavigator({
Home: {
screen: MetaTabNavigator,
navigationOptions: {
drawer: {
label: 'Drawer',
icon: ({ tintColor }) => <Icon name="rocket" size={24} />
},
},
}
},
{
contentComponent: props => <Menu {...props} />
}
);
AppRegistry.registerComponent('myApp', () => MetaDrawerNavigator);
MachineNavigator
const MachineNavigator = StackNavigator({
Main: {
screen: MachineOverview,
navigationOptions: ({ navigation }) => ({
title: "Dashboard",
headerLeft: (
<TouchableOpacity onPress={() => navigation.navigate("DrawerOpen")}>
<IOSIcon name="ios-menu" size={30} />
</TouchableOpacity>
),
headerStyle: { paddingRight: 10, paddingLeft: 10 }
})
},
Category: {
screen: Category,
navigationOptions: (props) => ({
title: "Kategorie",
})
},
ItemDetail: {
screen: ItemDetail,
navigationOptions: (props) => ({
title: "Video",
})
}
})
export default MachineNavigator;
According to this issue, you can add to the tab configuration the tabBarLabel property to control the label:
const MetaTabNavigator = TabNavigator({
Dashboard: {
screen: MachineNavigator, navigationOptions: {tabBarLabel: 'Dashboard'}
},
...