react navigation: change tabNavigator style based on redux store - react-native

So I'm trying to style the tabNavigator based on the store state:
import React from 'react';
import { connect } from 'react-redux';
import { TabNavigator } from 'react-navigation';
const Tabs = TabNavigator(
// Screens
{
Boards: {
screen: Boards,
navigationOptions: {
tabBarLabel: 'Boards',
tabBarIcon: () => <MaterialCommunityIcon name="bulletin-board" size={25} color="white" />,
},
},
Bookmarks: {
screen: Bookmarks,
navigationOptions: {
tabBarLabel: 'Bookmarks',
tabBarIcon: () => <EntypoIcon name="bookmarks" size={25} color="white" />,
},
},
Settings: {
screen: Settings,
navigationOptions: {
tabBarLabel: 'Settings',
tabBarIcon: () => <MaterialCommunityIcon name="settings" size={25} color="white" />,
},
},
},
// TabNavigator configuration
{
tabBarPosition: 'bottom',
tabBarOptions: {
showIcon: true,
showLabel: false,
renderIndicator: () => null,
style: {
// TODO: Make tabNavigation color change based on current selected theme.
backgroundColor: this.props.theme === 'default' ? 'black' : 'red',
},
},
},
);
const mapStateToProps = state => {
return {
theme: state.configuration.theme,
};
};
export default connect(mapStateToProps)(Tabs);
But when I try to use this.props.theme I get: undefined is not an object (evaluating 'this.props.theme') I guess this happens because tabNavigator doesn't accept props or something like that, so I can't connect tabNavigator to the redux store, so how can I implement what I'm trying to do?
Edit 1
After trying to resolve this using a custom tab bar in the way that was suggested above, this error pops up:
And the code:
TabBar.js
import React from 'react';
import { connect } from 'react-redux';
import { TabBarBottom } from 'react-navigation';
const TabBar = ({ theme }) => (
<TabBarBottom style={{ backgroundColor: theme === 'dark' ? 'black' : 'red' }} />
);
const mapStateToProps = state => ({
theme: state.configuration.theme,
});
export default connect(mapStateToProps)(TabBar);
router.js
import { TabNavigator } from 'react-navigation';
import TabBar from './components/TabBar';
import Boards from './screens/Boards';
import Settings from './screens/Settings';
import Bookmarks from './screens/Bookmarks';
const Tabs = TabNavigator(
// Screens
{
Boards: {
screen: Boards,
navigationOptions: {
tabBarLabel: 'Boards',
tabBarIcon: () => <MaterialCommunityIcon name="bulletin-board" size={25} color="white" />,
},
},
Bookmarks: {
screen: Bookmarks,
navigationOptions: {
tabBarLabel: 'Bookmarks',
tabBarIcon: () => <EntypoIcon name="bookmarks" size={25} color="white" />,
},
},
Settings: {
screen: Settings,
navigationOptions: {
tabBarLabel: 'Settings',
tabBarIcon: () => <MaterialCommunityIcon name="settings" size={25} color="white" />,
},
},
},
// TabNavigator configuration
{
tabBarPosition: 'bottom',
tabBarComponent: TabBar,
},
);
export default Tabs;

You can create your own tab bar, hook it up to the navigator and to redux.
const MyAwesomeTabBar = ({theme}) => (
<View>
...
</View>
)
export default connect(mapStateToProps)(MyAwesomeTabBar);
And then in your navigator definition:
const Tabs = TabNavigator(
// Screens
{
...
},
// TabNavigator configuration
{
tabBarPosition: 'bottom',
tabBarComponent: MyConnectedAwesomeTabBar
},
);
As for the separation of presentational/functional components - I think that it's not so much that not doing it is bad practice, as much as doing it is good practice. And, you can pretty easily separate it here as well, just have MyAwesomeTabBar be your functional component, which uses a bunch of presentational ones.

Related

How to display tab bar icon in react navigation 4

I want to display bottom tab tar in react-navigation 4, but there is no luck to make it happen, even I use it. Can anyone help me to find the problem with my code or which option should I use?
static navigationOptions = {
title: "Map",
tabBarIcon: ({ tintColor }) => {
return <Icon name="home" size={30} color={tintColor} />;
}
}
in any component screen, it does still not work.
Here is my router
I want to apply the bottom tab icon to homescreen
const MainAuthenticated = createAppContainer(
createBottomTabNavigator(
{
main: {
screen: createBottomTabNavigator({
Marketplace: {
screen: createStackNavigator({
home: {
screen: HomeScreen,
},
profile: { screen: Profile },
business: { screen: MyBusiness },
logout: { screen: Logout },
itemlist: { screen: ItemList },
itemcreate: { screen: ItemCreate },
itemdetail: { screen: ItemDetail },
businesscreate: { screen: BusinessCreate },
businessdetail: { screen: MyBusinessDetail },
}),
},
XOrders: { screen: OrderScreen },
Favorite: { screen: FavoriteScreen },
}),
},
},
{
defaultNavigationOptions: {
tabBarVisible: false,
},
},
),
);
Here is the working code to add the bottom tab bar icon in react-navigation v4
import Ionicons from 'react-native-vector-icons/Ionicons';
import { createAppContainer } from 'react-navigation';
import { createBottomTabNavigator } from 'react-navigation-tabs';
export default createBottomTabNavigator(
{
Home: HomeScreen,
Settings: SettingsScreen,
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
const { routeName } = navigation.state;
let IconComponent = Ionicons;
let iconName;
if (routeName === 'Home') {
iconName = focused
? 'ios-information-circle'
: 'ios-information-circle-outline';
// Sometimes we want to add badges to some icons.
// You can check the implementation below.
IconComponent = HomeIconWithBadge;
} else if (routeName === 'Settings') {
iconName = focused ? 'ios-list-box' : 'ios-list';
}
// You can return any component that you like here!
return <IconComponent name={iconName} size={25} color={tintColor} />;
},
}),
tabBarOptions: {
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
},
}
);
If you wanted to use some .png or jpeg or some other image file instead of vector icons just replace this
<IconComponent name={iconName} size={25} color={tintColor} /> // replace this with below
<Image source={require('your image path')} style={{height: 30, width: 30}} />

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 Navigation(V3):How to set navigation function in custom router file?

I have a Router.js to set all of my components with react-navigation.
When I click the component FloorScreen headerRight button will show click alert.
Now I want to change it like this.props.navigation.navigate.goBack();
Router.js:
import React, { Component } from 'react';
import { Image, TouchableOpacity } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
// and import some screen...
const Router = createStackNavigator({
WelcomeScreen: {
screen: WelcomeScreen,
navigationOptions: () => ({
header: null
}),
},
HomeScreen: {
screen: HomeScreen
},
FloorScreen: {
screen: FloorScreen,
navigationOptions: {
drawerLabel: 'test',
headerStyle: {
backgroundColor: commonColor.appBackgroundColor,
},
headerRight: (
<TouchableOpacity onPress={() => alert('click')}>
<Image style={{ width: 20, height: 20}} source={BackIcon} />
</TouchableOpacity>
)
}
}
},
{
initialRouteName: 'WelcomeScreen',
headerMode: 'screen'
});
export default createAppContainer(Router);
I know I can set the code in FloorScreen.js to achieve it:
static navigationOptions = ({ navigation }) => ({
headerRight : (
<TouchableOpacity onPress={() => { navigation.goBack() }}>
<Image />
</TouchableOpacity>
),
});
Is possible to set the code in my Router.js ? Or set the code in FloorScreen.js is the only way to do it ?
Any help would be appreciated.
You can implement it at navigationOption
navigationOptions: () => ({
tabBarLabel: strings.labelJobs,
tabBarIcon: ({ tintColor }) => (
<Icon type="FontAwesome" name="someName" />
),
tabBarOnPress: ({ navigation, defaultHandler }) => {
if (navigation.state.index > 0) {
navigation.dispatch(StackActions.popToTop());
}
defaultHandler();
}
})
The above is to demonstrate how to display first screen of stack, just to show you how you can access navigation within navigationOptions

component render one time when click tab on drawerNavigator in react native

I'm new on react native getting issue on calling component. Whenever click on drawer navigator tab first time component render and API called But when back to the home page and again call that component API not called. I want to recall that function.
Here is my drawer navigator code :
const AppDrawerNavigator = createDrawerNavigator({
Home: {
screen: Home,
navigationOptions: {
drawerLabel: 'Home',
drawerIcon: () => (
<Icon name="home" size={20} color="#0f1f7b" />
)
},
},
PreviousInspection: {
screen: PreviousInspection,
navigationOptions: {
drawerLabel: 'Previous Inspection',
drawerIcon: () => (
<Icon name="file" size={20} color="#0f1f7b" />
)
},
},
Logout: {
screen: Logout,
navigationOptions: {
drawerLabel: 'Logout',
drawerIcon: () => (
<Icon name="sign-out" size={20} color="#0f1f7b" />
)
},
}
},
{
drawerBackgroundColor: "#fff",
contentOptions: {
activeTintColor: '#000',
inactiveTintColor: '#000',
activeBackgroundColor: '#bfc7f3',
itemStyle: {
fontSize: 12,
},
},
});
react-navigation has a withNavigationFocus HOC which provides an isFocused prop to your component. You can use that to determine when a certain screen has become visible.
import { withNavigationFocus } from 'react-navigation';
class YourScreen extends React.Component {
render() {
...
}
componentDidUpdate(prevProps) {
if (this.props.isFocused && !prevProps.isFocused) {
// Screen has now come into focus, call your method here
}
}
}
export default withNavigationFocus(YourScreen)

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'}
},
...