Tab Navigator On focussed - react-native

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.

Related

Is there a option to disable tabbar buttons

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}
/>

react-navigation goBack between stack navigators

In my React native app I am using "react-navigation": "^3.11.0".
I have top level bottomTabNavigator
const TabNavigator = createBottomTabNavigator({
Main: {
screen: balanceStack,
navigationOptions: {
tabBarLabel: I18n.t("balanceTabLabel"),
tabBarIcon: ({ tintColor}: {tintColor: string}) => (
<Icon name="home" style={{color: tintColor}} />
)
}
},
ServicesStack: {
screen: servicesStack,
navigationOptions: {
tabBarLabel: I18n.t("servicesTabLabel"),
tabBarIcon: ({ tintColor}: {tintColor: string}) => (
<Icon name="list-box" style={{color: tintColor}} />
)
}
},
}, {
tabBarOptions: {
activeTintColor: WHITE_COLOR,
inactiveTintColor: WHITE_COLOR,
style: {
backgroundColor: PRIMARY_COLOR,
}
},
backBehavior: 'history',
swipeEnabled: true,
});
And stack navigators for each tab:
const balanceStack = createStackNavigator({
Balance: {
screen: MainScreen,
},
FullBalance: {
screen: FullBalanceScreen,
},
Payment: {
screen: PaymentScreen,
},
ServiceView: {
screen: ViewServiceScreen,
},
}, {
initialRouteName: 'Balance',
headerMode: 'none',
});
const servicesStack = createStackNavigator({
AllServices: {
screen: AllServicesScreen,
},
ServiceView: {
screen: ViewServiceScreen,
},
ServiceOptionAction: {
screen: ServiceOptionsActionScreen,
}
}, {
initialRouteName: 'AllServices',
headerMode: 'none',
});
I want that my navigation for all tabs will be common, not divided per stack.
For example
when I navigate
Balance->FullBalanceScreen->AllServices(by clicking Services tab)->ServiceView
If I click back button (call goBack()) one time I will back to AllServices. But if I click back second time, I don't navigate to FullBalanceScreen, because it's in another stack. How can I do this?
Finally, I didn't find solution with TabNavigator and replace it with top level StackNavigator.
Also I use custom bottom Tabs component and set onPress for tab button to
onPress () {
        this.props.navigation.navigate (this.props.navigateRouteName)
    }
Where navigateRouteName is routeName to target stack route.
If you define a custom header (either for the top level nav, or duplicated for each stack) then navigation object will have the global history (not just for each stack) so clicking back will jump between tabs
<A.Navigation screenOptions={{header: ({nav}) => <CustomHeader nav/>}>
and in you header you can conditionally show a back button and add handler
if (nav.canGoBack()) // show button
... nav.goBack() // onPress

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
}

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)