Block/Disable tabs in TabNavigator - react-navigation - react-native

I have a TabNavigator as shown in the picture.
Header Image
I am using TabNavigator for creating these tabs as below.
const Tab_Navigator = TabNavigator({
First:{
screen: First,
},
Second:{
screen: Second,
},
Third:{
screen: Third,
},
Now I want to block/disable "Second" and "Third" tabs. It should be visible but one shouldn't able to navigate to them.
I tried blocking these tabs as shown here but I guess I am missing something. My try:
Tab_Navigator.router.getStateForAction = (action, state) => {
if( action.type === NavigationActions.navigate({ routeName: "Second"}) ||
action.type === NavigationActions.navigate({ routeName: "Third"}))
{
return null;
}
return Byte.router.getStateForAction(action, state);
};

In this case, the action.type = "Navigation/NAVIGATE" and action.routeName is the name of your tab. It is just a little different from the ReactNavigation Routers example. The following should work:
const defaultGetStateForAction = Tab_Navigator.router.getStateForAction;
Tab_Navigator.router.getStateForAction = (action, state) => {
if ((action.type === NavigationActions.NAVIGATE) &&
(action.routeName === "Second" || action.routeName === "Third") {
return null;
}
return defaultGetStateForAction(action, state);
};
EDIT: Here is an image of the the Chrome Debugger stopped at a breakpoint in a very similar piece of code(tab names are different), but it shows the values of the "action" object being passed into this function.

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/

You have to use tabBarOnPress propert under defaultNavigationOptions, and check the route name to which you dont want to navigate return them null else return defaultHandler. Please check the following code
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();
},})

This is my solution to disable and completely take control over the tabBarButton in React Navigation 6.
// Defining the disabled tabBarButton component
//
const DisabledTabBarButton = ({ style, ...props }: BottomTabBarButtonProps) => (
<Pressable disabled style={[{ opacity: 0.2 }, style]} {...props} />
)
const Tab = createBottomTabNavigator()
const Router = () => (
<Tab.Navigator>
<Tab.Screen name="Screen 1" />
<Tab.Screen name="Screen 2"
options={{
// Applying the disabled button
tabBarButton: DisabledTabBarButton,
}}
/>
</Tab.Navigator>
export default Router

Related

React Navigation upgrade v4 to v6 - navigationOptions and listener

Previously with React Navigation v4, I have use route.params.scrollToTop() to pass the function I want to call when tabBarOnPress. The function content is not related to scrollToTop. After upgrading to React Navigation v6, I have changed the code. However, with all the changes, the scrollToTop function only called once. Once I switch to another Tab and go back to ScreenB, the componentDidMount do not trigger again. I would like to call the function every time I tap the tab just like how it respond in v4, no matter which screen I'm switching from.
v4
const RouteConfigs = {
ScreenA:{
screen: ComponentA,
},
ScreenB:{
screen: ComponentB,
navigationOptions: {
tabBarOnPress: (event) => {
const { navigation } = event;
event.defaultHandler();
if (navigation.isFocused() && navigation.state.params && navigation.state.params.scrollToTop) {
navigation.state.params.scrollToTop();
}
},
}
}
}
const Tabs = createBottomTabNavigator(RouteConfigs, TabNavigatorConfig)
Tabs.navigationOptions = ({navigation}) => {
const {routeName} = navigation.state.routes[navigation.state.index]
return{
header: null,
}
}
v6
Navigation
const Tab = createBottomTabNavigator();
<Tab.Navigator>
<Tab.Screen
name="ScreenA"
component={ComponentA}
>
<Tab.Screen
name="ScreenB"
component={ComponentB}
listeners={({ navigation, route, params}) => ({
tabPress: (e) => {
if (navigation.isFocused() && route.params && route.params.scrollToTop) {
route.params.scrollToTop();
}
},
})}
>
</Tab.Navigator>
I did not change anything in ComponentB
...
componentDidMount(){
this.props.navigation.setParams({
scrollToTop: this.scrollToTop,
});
}
scrollToTop = ()=>{
console.log("Press")
this.setState(()=> ({...}))
}
...
I am not sure is it because I did not add the part. However I have no idea how to add it under v6.
Tabs.navigationOptions = ({navigation}) => {
const {routeName} = navigation.state.routes[navigation.state.index]
return{
header: null,
}
}

Sending params with navigation.goBack in react navigation

hellow , how to send params navigation go back ?
const Onsubmit = (data, details = null) => {
console.log(details.formatted_address);
route.params.onPlaceChosen(
route.params.id,
details.formatted_address,
details.geometry
);
navigation.goBack();
};
here I want to pass the value from details.formatted_address to page B.
How to ?
If you are navigating from Screen A to Screen B, and when you want to go back to Screen A with running a callback in again Screen A, below is what you need to do:
In your Screen A (Source)
...
const onPlaceChosen = (params) => {
// here is your callback function
}
...
navigation.navigate('ScreenB', { onPlaceChosen })
...
In your Screen B (Destination)
..
const Onsubmit = (data, details = null) => {
navigation.state.params.onPlaceChosen(
route.params.id,
details.formatted_address,
details.geometry
);
navigation.goBack();
};
...
I did something like this based on https://reactnavigation.org/docs/5.x/hello-react-navigation/#passing-additional-props
const [params, setParams] = useState({});
<Stack.Navigator>
<Stack.Screen name="One">
{props => <FirstScreen {...props} paramsFromTwo={params} />}
</Stack.Screen>
<Stack.Screen name="Two">
{props => <SecondScreen {...props} onGoBack={(paramsFromSecond} => setParams(paramsFromSecond)} />}
</Stack.Screen>
</Stack.Navigator>
I did this way:
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { post: postText },
merge: true,
});
}}
It was extracted from:
https://reactnavigation.org/docs/params#passing-params-to-a-previous-screen
In my case, using navigation and route passed by props, the solution was:
route.params.onFilter({
route.params.id,
details.formatted_address,
details.geometry
});
navigation.goBack();

How to pass a null screen in Tab Navigation in React Navigation 5

In previous versions of react navigation, we could do this
const CustomTabNavigator = createBottomTabNavigator({
FirstTab: {
screen: FirstScreen,
},
AddButton: {
screen: () => null,
navigationOptions: () => ({
tabBarIcon: (<AddButton/>),
tabBarOnPress: () => {}
})
},
SecondTab: {
screen: SecondScreen,
}
}
Trying to replicate this with react-navigation is causing errors as it won't accept null. Anyone know a way around this?
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs'
const BottomTabNavigator = createBottomTabNavigator()
<BottomTabNavigator.Navigator>
<BottomTabNavigator.Screen
name="FirstTab"
component={FirstScreen}
/>
<BottomTabNavigator.Screen
name="Add"
component={null}
options: () => ({
tabBarIcon: (<AddButton/>),
tabBarOnPress: () => {}
})
/>
<BottomTabNavigator.Screen
name="Second Tab"
component={SecondScreen}
/>
</BottomTabNavigator.Navigator>
Try this, I originally tried to pass it in as an inline function but I got a warning as well. This solved my problem.
Example:
const AddButton = () => {
return null
}
<Tab.Screen name="AddButton" component={AddButton}/>

Change Bottom Tab Bar based on state in React Navigation with navigationOptions

I want to change the bottom tabs on the screen based on what features are enabled. This feature list is populated via a login API call.
Currently I have the following:
const TabRouteConfig = {
Home: DashboardScreen,
FeatureA: FeatureA,
FeatureZ: FeatureZ,
};
const TabNavigatorConfig = {
initialRouteName: 'Home',
order: [
'Home',
'Feature A',
],
tabBarPosition: 'bottom',
lazy: true,
};
const TabNavigator1 = createBottomTabNavigator(
TabRouteConfig,
TabNavigatorConfig,
);
// Set the tab header title from selected route
// https://reactnavigation.org/docs/en/navigation-options-resolution.html#a-stack-contains-a-tab-navigator-and-you-want-to-set-the-title-on-the-stack-header
TabNavigator1.navigationOptions = ({ navigation }) => {
const { index, routes } = navigation.state;
const { routeName } = routes[index];
return {
headerTitle: routeName,
};
};
const TabNavigator2 = createBottomTabNavigator(
TabRouteConfig,
{
...TabNavigatorConfig,
order: [
'Home',
'Feature Z',
]
);
TabNavigator2.navigationOptions = TabNavigator1.navigationOptions;
const Stack = createStackNavigator({
Main: {
screen: props => (props.screenProps.hasFeature ?
<TabNavigator1 /> : <TabNavigator2 />
)
},
})
const WrappedStack = props => (
<View style={styles.container}>
<Stack
screenProps={{ hasFeature: props.hasFeature }}
/>
</View>
);
const mapStateToProps = (state, props) => {
return {
...props,
hasFeature: state.hasFeature,
};
};
export default connect(mapStateToProps, null)(WrappedStack);
This mostly works - it dynamically switches between TabNavigator1 and TabNavigator2 based on hasFeature, but it no longer honors the navigationOptions placed on the TabNavigators and the headerTitle is not set.
Is there a better way to do this?
It's an antipattern to render more than one navigator simultaneously as the navigation states of those navigators will be completely separated, and you will not be able to navigate to one from another.
You can use tabBarComponent option to achieve what you want. Hope you can get the idea from below example:
import { createBottomTabNavigator, BottomTabBar } from 'react-navigation-tabs';
const TabNavigator = createBottomTabNavigator(
TabRouteConfig,
{
tabBarComponent: ({ navigation, ...rest }) => {
const filteredTabNavigatorRoutes = navigation.state.routes.filter(route => isRouteAllowed(route));
return (
<BottomTabBar
{...rest}
navigation={{
...navigation,
state: { ...navigation.state, routes: filteredTabNavigatorRoutes },
}}
/>
);
},
},
);
NOTES:
You don't have to install react-navigation-tabs separately. It is automatically installed with react-navigation 2.0.0+.
isRouteAllowed is the function which returns true or false based on whether to show that route or not. Make sure to only check the top level routes in that object.
TabRouteConfig should contain all possible tabs, and this logic only hides the route from the TabBar visually. So, you can still programmatically navigate to all routes. Therefore, you might need additional logic in each of those screens to decide whether to render them based on hasFeature.

how to use tabBarOnPress in tabnavigator react native

i am stuck in big problem that is i wants onPress event when i clicked on tab.
my code is here:-
static navigationOptions = ({navigation, screenProps}) => {
const params = navigation.state.params || {};
console.log("Params:-",params);
return {
title:Strings.title_dashboard,
headerStyle:{ backgroundColor: Colors.header_blue},
headerTitleStyle:HeaderStyle.titleCenter,
tabBarOnPress: (tab, jumpToIndex) => {
console.log("Tab Click",tab);
jumpToIndex(tab.index);
navigation.state.params.onFocus()
},
headerRight:<TouchableOpacity onPress={()=>Alert.alert(Strings.avanza,Strings.under_imple)}><View><Image source={{uri: "filter_icon"}} style={{height: 18, width: 18,marginRight:10,}} /></View></TouchableOpacity>,
}
}
at here i set the Params like this in componentDidMount:
this.props.navigation.setParams({
handleGrid: this.callServer.bind(this)
})
getting an error here not able to get this click event.
Help me please.
Thank you.
This is my approach. It works for the version 5.x.x of react-navigation:
const BottomTab = createBottomTabNavigator();
const Tabs = props => (
<BottomTab.Navigator
initialRouteName="Foo"
...
<BottomTab.Screen
name="Foo"
component={Foo}
initialParams={props.route.params}
listeners={{
tabPress: e => {
// e.preventDefault(); // Use this to navigate somewhere else
console.log("Foo tab bar button pressed")
},
}}
/>
</BottomTab.Navigator>
);
Read more about listeners.
For version 3.x.x and I hope for the 4th as well please use this one.
let Tabs = createBottomTabNavigator(
{
FooTab: Foo,
},
{
initialRouteName: "FooTab",
defaultNavigationOptions: ({ navigation }) => ({
tabBarOnPress: ({ navigation, defaultHandler }) => {
console.log('onPress:', navigation.state.routeName);
defaultHandler()
},
}),
}
);
For version 2.x.x please use navigationOptions instead of the defaultNavigationOptions.
This is working for me,
static navigationOptions = ({ navigation }) => {
return {
tabBarOnPress: ({previousScene, scene, jumpToIndex}) => {
const { route, index, focused} = scene;
if(focused){
navigation.state.params.scrollToTop()
}
jumpToIndex(0)
}
}
};
I used stack navigator
const Stack = createStackNavigator({
ScreenA: {
screen:ScreenA ,
navigationOptions: () => ({
header: null
}),
},
ScreenB: {
screen:ScreenB ,
navigationOptions: () => ({
header: null
}),
},
});
//Added tabBarOnPress
https://reactnavigation.org/docs/en/stack-actions.html
the popToTop action takes you back to the first screen in the stack, dismissing all the others. It's functionally identical to StackActions.pop({n: currentIndex}).
import { StackActions } from 'react-navigation';
let Tabs = createBottomTabNavigator(
{
FooTab: Foo,
},
{
initialRouteName: "FooTab",
defaultNavigationOptions: ({ navigation }) => ({
tabBarOnPress: ({ navigation, defaultHandler }) => {
// to navigate to the top of stack whenever tab changes
navigation.dispatch(StackActions.popToTop());
defaultHandler();
]},
}),
}
);