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

Viewed 6605

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?

2 Answers

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.

Navigates to an existing screen in the tab navigator. The method accepts the following arguments:

name - string - Name of the route to jump to. params - object - Screen params to use for the destination route.

Try this example on Snack

navigation.jumpTo('Profile', { owner: 'Zohaib' });
Related