0

I'm trying to use a custom component (tabBarComponent property) for my React Native app using createMaterialTopTabNavigator from "react-native-tabs", the component being BottomNavigation from "react-native-paper".

I have copied nearly all of the code from https://github.com/react-navigation/material-bottom-tabs , as it is the only source I have found for using BottomNavigation as a custom component

I have 3 main pieces of code: TabNavigator (component)

class CustomTabNavigator extends React.Component {
_getColor = ({ route }) => {
  const { descriptors } = this.props;
  const descriptor = descriptors[route.key];
  const options = descriptor.options;

  if (this.context === 'dark' && options.tabBarColorDark) {
    return options.tabBarColorDark;
  } else if (options.tabBarColorLight) {
    return options.tabBarColorLight;
  } else {
    return options.tabBarColor;
  }
};

_getactiveColor = () => {
  let { activeColor, activeColorLight, activeColorDark } = this.props;

  if (this.context === 'dark' && activeColorDark) {
    return activeColorDark;
  } else if (activeColorLight) {
    return activeColorLight;
  } else {
    return activeColor;
  }
};

_getInactiveColor = () => {
  let { inactiveColor, inactiveColorLight, inactiveColorDark } = this.props;

  if (this.context === 'dark' && inactiveColorDark) {
    return inactiveColorDark;
  } else if (inactiveColorLight) {
    return inactiveColorLight;
  } else {
    return inactiveColor;
  }
};

_getBarStyle = () => {
  let { barStyle, barStyleLight, barStyleDark } = this.props;

  return [barStyle, this.context === 'dark' ? barStyleDark : barStyleLight];
};

_isVisible() {
  const { navigation, descriptors } = this.props;
  const { state } = navigation;
  const route = state.routes[state.index];
  const options = descriptors[route.key].options;
  return options.tabBarVisible;
}

_renderIcon = ({
  route,
  focused,
  color,
}) => {
  return this.props.renderIcon({ route, focused, tintColor: color });
};

render() {
  const {
    navigation,
    descriptors,
    ...rest
  } = this.props;

  const activeColor = this._getactiveColor();
  const inactiveColor = this._getInactiveColor();
  const barStyle = this._getBarStyle();

  const isVisible = this._isVisible();
  const extraStyle =
    isVisible === false
      ? {
          display: 'none',
          position: undefined,
        }
      : null;

  return (
    <BottomNavigation
      {...rest}
      activeColor={activeColor}
      inactiveColor={inactiveColor}
      renderIcon={this._renderIcon}
      barStyle={[barStyle, extraStyle]}
      navigationState={navigation.state}
      getColor={this._getColor}
    />
  )
}
}

TabNavigation (navigator)

class CustomTabNavigation extends React.Component {
_renderScene = ({ route }) => {
  const { screenProps, descriptors } = this.props;
  const descriptor = descriptors[route.key];
  const TabComponent = descriptor.getComponent();
  return (
    <SceneView
      screenProps={screenProps}
      navigation={descriptor.navigation}
      component={TabComponent}
    />
  );
};

_renderIcon = ({
  route,
  focused,
  tintColor,
  horizontal = false,
}) => {
  const { descriptors } = this.props;
  const descriptor = descriptors[route.key];
  const options = descriptor.options;

  if (options.tabBarIcon) {
    return typeof options.tabBarIcon === 'function'
      ? options.tabBarIcon({ focused, tintColor, horizontal })
      : options.tabBarIcon;
  }

  return null;
};

_getLabelText = ({ route }) => {
  const { descriptors } = this.props;
  const descriptor = descriptors[route.key];
  const options = descriptor.options;

  if (options.tabBarLabel) {
    return options.tabBarLabel;
  }

  if (typeof options.title === 'string') {
    return options.title;
  }

  return route.routeName;
};

_getAccessibilityLabel = ({ route }) => {
  const { descriptors } = this.props;
  const descriptor = descriptors[route.key];
  const options = descriptor.options;

  if (typeof options.tabBarAccessibilityLabel !== 'undefined') {
    return options.tabBarAccessibilityLabel;
  }

  const label = this._getLabelText({ route });

  if (typeof label === 'string') {
    const { routes } = this.props.navigation.state;
    return `${label}, tab, ${routes.indexOf(route) + 1} of ${
      routes.length
    }`;
  }

  return undefined;
};

_getTestID = ({ route }) => {
  const { descriptors } = this.props;
  const descriptor = descriptors[route.key];
  const options = descriptor.options;

  return options.tabBarTestID;
};

_getBadge = ({ route }) => {
  const { descriptors } = this.props;
  const descriptor = descriptors[route.key];
  const options = descriptor.options;

  return options.tabBarBadge;
};

_makeDefaultHandler = ({
  route,
  navigation,
}) => () => {
  if (navigation.isFocused()) {
    if (route.hasOwnProperty('index') && route.index > 0) {
      navigation.dispatch(StackActions.popToTop({ key: route.key }));
    } else {
      navigation.emit('refocus');
    }
  } else {
    this._jumpTo(route.routeName);
  }
};

_handleTabPress = ({ route }) => {
  this._isTabPress = true;
  Promise.resolve().then(() => (this._isTabPress = false));

  const { descriptors } = this.props;
  const descriptor = descriptors[route.key];
  const { navigation, options } = descriptor;

  const defaultHandler = this._makeDefaultHandler({ route, navigation });

  if (options.tabBarOnPress) {
    options.tabBarOnPress({ navigation, defaultHandler });
  } else {
    defaultHandler();
  }
};

_handleIndexChange = index => {
  if (this._isTabPress) {
    this._isTabPress = false;
    return;
  }

  this._jumpTo(this.props.navigation.state.routes[index].routeName);
};

_jumpTo = routeName => {
  const { navigation } = this.props;

  navigation.dispatch(
    SwitchActions.jumpTo({
      routeName,
      key: navigation.state.key,
    })
  );
};

_isTabPress = false;

render() {
  const {
    descriptors,
    navigation,
    screenProps,
    navigationConfig,
  } = this.props;
  const { state } = navigation;
  const route = state.routes[state.index];
  const descriptor = descriptors[route.key];
  const options = {
    ...navigationConfig,
    ...descriptor.options,
  };

  return (
    <CustomTabNavigator
      {...options}
      getLabelText={this._getLabelText}
      getAccessibilityLabel={this._getAccessibilityLabel}
      getTestID={this._getTestID}
      getBadge={this._getBadge}
      renderIcon={this._renderIcon}
      renderScene={this._renderScene}
      onIndexChange={this._handleIndexChange}
      onTabPress={this._handleTabPress}
      navigation={navigation}
      descriptors={descriptors}
      screenProps={screenProps}
    />
  );
}
}

Main navigation

const TabNavigator = createStackNavigator(
{
  TabsStack: {
    ...,
    {
      ...,
      tabBarComponent: ({ props }) => <CustomTabNavigation {...props} />
    })
  },
}
);

I was expecting to be able to use the swipeEnabled feature (animated transition to and from each screen) from createMaterialTopTabNavigator from "react-navigation", as well as having a better-looking tab bar, but it says that 'navigation.state is undefined' in the render function of CustomTabNavigation (line const { state } = navigation;), but I can't see the reason why.

Okie
  • 67
  • 1
  • 11

1 Answers1

0

I think there as error here: tabBarComponent: ({ props }) => <CustomTabNavigation {...props} /> should be tabBarComponent : props => <CustomTabNavigation {...props} /> or just tabBarComponent: CustomTabNavigation

BottomNavigation from paper is not a tab bar component, it's a full tab navigation component. You could use it like a tab bar by not rendering content I guess.

But consider that neither Android, nor iOS design guidelines don't add swipe gestures to bottom tabs, so the UX will be inconsistent. Usually top tab bar has swipe gesture because it's harder to reach the top bar with finger.

satya164
  • 9,464
  • 2
  • 31
  • 42