Reputation: 177
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.
Upvotes: 1
Views: 1181
Reputation: 2449
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.
Upvotes: 2