react-native-vertical-tab-view icon indicating copy to clipboard operation
react-native-vertical-tab-view copied to clipboard

Integration with stackNavigator

Open phonegap20 opened this issue 7 years ago • 4 comments

Hi,

How can i implement stack navigator in SceneMap instead of passing the component name.

In SceneMap it only accepts the react component not the stack navigator which is causing this.props.navigation undefined in nested component.

like i want to pass it in similar way.

const HomeNavigator = createStackNavigator( { Home: HomeScreen, Details: DetailsScreen }, { initialRouteName: "Home" } );

_renderScene = SceneMap({ '1': HomeNavigator, '2': Screen2, '3': Screen3, '4': Screen4, '5': Screen5, '6': Screen6 });

It only accepts the react class component not the StackNavigator.

phonegap20 avatar Mar 24 '19 17:03 phonegap20

Hi @phonegap20 - sorry for the delay in getting back to you. We do this today and it works well. Here is how I do it. Its really simple, just pass a function in like this:

_renderScene = SceneMap({
  '1': () => HomeNavigator
...

DaKaZ avatar Apr 29 '19 22:04 DaKaZ

Thanks for your feedback.

Can you share the complete working example please ?

phonegap20 avatar Apr 30 '19 14:04 phonegap20

I am sorry to inform you that i also tried it at my end and SceneMap is from react-native-tab-view library and does not exist in react-native-vertical-tab-view.

Kindly share a working example.

Thanks a lot in advance !

phonegap20 avatar Apr 30 '19 15:04 phonegap20

React native tab view is a dependency of this project, but this project exports it as well. Here is our implementation, it may be a bit more complex then you looking for because we also use react-native-web and enable different tabs on production/staging versus development/preview environments, but you can see our implementation here. Note many of the imported screens are other navigators like Stack and Tabs:

// @flow

import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { Animated, StyleSheet, Platform } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { getBottomSpace } from 'react-native-iphone-x-helper';
import {
  TabView,
  TabViewVertical,
  TabBar,
  TabBarVertical,
  SceneMap,
} from 'react-native-vertical-tab-view';
import Config from 'react-native-config';

import type { Element, ComponentType } from 'react';
import type { NavigationScreenProp } from 'react-navigation';
// we have had to add the RNTV files under flowconfig [untyped] because of
// unrelated flow errors in RNTV source. This means that these types are
// treated as 'any' until we can remedy this. If you need to edit this file
// uncomment the [untyped] section of flowconfig so that flow will provide
// you meaningful type check feedback during development. Unfortunately, you
// need to enable the [untyped] section otherwise jenkins will fail the build
// due to the RNTV flow errors.
import type {
  Scene,
  NavigationState,
  Route,
  SceneRendererProps
} from 'react-native-vertical-tab-view';

import type { LastActivityUpdateAcType } from '../../ducks/appGlobal';

import { actions } from '../../ducks';

import DashboardScreen from './Tabs/DashboardScreen';
import EMScreen from './Tabs/EMScreen';
import HRScreen from './Tabs/HRScreen';
import PerformanceScreen from './Tabs/PerformanceScreen';
import FacilityScreen from './Tabs/FacilityScreen';
import FinanceScreen from './Tabs/FinanceScreen';
import { Colors } from '../../styles';

import type { ScreenPropsType } from '../AppNavigator';

type ReduxStorePropsType = {
  updateLastActivity: LastActivityUpdateAcType
};

type PropsType = ReduxStorePropsType & {
  screenProps: ScreenPropsType,
  navigation: NavigationScreenProp<*>,
  onTabChange: (title: string) => void
};

type TabScreenRouteType = Route<{
  key: string,
  title: string,
  icon: string,
  path: string,
  publicallyAvailable: boolean,
  scene: ComponentType<*>
}>;

type RoutesType = Array<TabScreenRouteType>;

type ScenesType = {
  [key: string]: ComponentType<*>
};

type StateType = {
  scenes: ScenesType,
  tabState: NavigationState<TabScreenRouteType>
};

type TabScreenSceneRenderPropsType = SceneRendererProps<TabScreenRouteType>;
type TabScreenSceneType = Scene<TabScreenRouteType>;
type TabBarCallbackType = (props: TabScreenSceneType) => ?Element<*>;

const performanceTitle = 'PERFORMANCE';
const financeTitle = 'FINANCE';

function getScenesFromRoutes(routes: RoutesType): ScenesType {
  return routes.reduce((s, r): ScenesType => (
    { ...s, [r.key.toString()]: r.scene }
  ), {});
}

class TabsScreenIso extends PureComponent<PropsType, StateType> {
  static defaultProps = {
    screenProps: { orientation: 'PORTRAIT', webParams: {} }
  };

  constructor(props: PropsType) {
    super(props);
    const routes = this._getRoutes();
    const scenes = getScenesFromRoutes(routes);
    let index = routes.findIndex((route): boolean => (
      route.path === props.screenProps.webParams.tab
    ));
    if (index < 0) index = 0;
    this.state = {
      // the entire state object is passed to the TabViewAnimated component in
      // render. That component handles the state from there on.
      tabState: {
        index,
        routes
      },
      scenes
    };
  }

  componentDidMount() {
    const { navigation, screenProps } = this.props;
    const { routes } = this.state.tabState;
    let { index } = this.state.tabState;

    const tab = (navigation.state.params && navigation.state.params.tab)
      || screenProps.webParams.tab;
    if (tab) {
      index = routes.map((route: TabScreenRouteType): string => route.path).indexOf(tab);
    }
    if (index < 0 || index >= routes.length) index = 0;
    // FIXME: why do we need a delay to make this work???
    setTimeout(() => {
      this._handleIndexChange(index);
    }, 100);
  }

  // this has to be in the class or `this` keyword will not work
  _getRoutes = (): RoutesType => {
    const allRoutes = [
      {
        key: '1',
        title: 'DASHBOARD',
        icon: 'tachometer',
        path: 'dashboard',
        publicallyAvailable: true,
        scene: (): Element<*> => <DashboardScreen />
      },
      {
        key: '2',
        title: 'EMERGENCY MANAGEMENT',
        icon: 'phone',
        path: 'emergency_management',
        publicallyAvailable: true,
        scene: (): Element<*> => (
          <EMScreen navigation={this.props.navigation} screenProps={this.props.screenProps} />
        )
      },
      {
        key: '3',
        title: financeTitle,
        icon: 'pie-chart',
        path: 'finance',
        publicallyAvailable: true,
        scene: (): Element<*> => <FinanceScreen navigation={this.props.navigation} />
      },
      {
        key: '4',
        title: performanceTitle,
        icon: 'line-chart',
        path: 'performance',
        publicallyAvailable: true,
        scene: (): Element<*> => (
          <PerformanceScreen navigation={this.props.navigation} />
        )
      },
      {
        key: '5',
        title: 'FACILITIES',
        icon: 'building',
        path: 'facilities',
        publicallyAvailable: false,
        scene: (): Element<*> => <FacilityScreen />
      },
      {
        key: '6',
        title: 'HUMAN RESOURCES',
        icon: 'users',
        path: 'human_resources',
        publicallyAvailable: false,
        scene: (): Element<*> => <HRScreen />
      }
    ];
    const publicRoutes = allRoutes.filter((r): boolean => r.publicallyAvailable);
    if (['production', 'staging'].includes(Config.FODLINK_ENV)) {
      return publicRoutes;
    }
    return allRoutes;
  };

  // index state is used by the TabViewAnimated to determine the active tab
  _handleIndexChange = (index: number) => {
    this.props.updateLastActivity();
    const tabState = { ...this.state.tabState, index };
    this.setState({ tabState }, () => {
      this.props.onTabChange(this.state.tabState.routes[index].title);
    });
  };

  _renderLabelFactory = (props: TabScreenSceneRenderPropsType): TabBarCallbackType => (
    ({ route }: TabScreenSceneType): Element<*> => {
      const index = props.navigationState.routes.indexOf(route);
      const inputRange
        = props.navigationState.routes.map((x: TabScreenRouteType, i: number): number => i);
      const outputRange = inputRange.map((inputIndex: number): string =>
        (inputIndex === index ? Colors.lightBlue : Colors.dhsWhite));
      const color = props.position.interpolate({
        inputRange,
        outputRange
      });
      return <Animated.Text style={[styles.label, { color }]}>{route.title}</Animated.Text>;
    }
  );

  _renderIconFactory = (props: TabScreenSceneRenderPropsType): TabBarCallbackType => (
    ({ route }: TabScreenSceneType): Element<*> => {
      const index = props.navigationState.routes.indexOf(route);
      const inputRange
        = props.navigationState.routes.map((x: TabScreenRouteType, i: number): number => i);
      const outputRange = inputRange.map((inputIndex: number): string =>
        (inputIndex === index ? Colors.lightBlue : Colors.dhsWhite));
      const color = props.position.interpolate({
        inputRange,
        outputRange
      });
      const AnimatedIcon = Animated.createAnimatedComponent(Icon);
      return <AnimatedIcon name={route.icon} size={30} style={[styles.icon, { color }]} />;
    }
  );

  _renderTabs = (sceneProps: SceneRendererProps<TabScreenRouteType>): Element<*> => {
    const landscape
      = this.props.screenProps.orientation
      && this.props.screenProps.orientation.split('-')[0] === 'LANDSCAPE';
    const SelectedTabBar = landscape ? TabBarVertical : TabBar;
    return (
      <SelectedTabBar
        {...sceneProps}
        renderLabel={this._renderLabelFactory(sceneProps)}
        renderIcon={this._renderIconFactory(sceneProps)}
        style={styles.tabbar}
        tabStyle={styles.tab}
        indicatorStyle={styles.indicator}
        scrollEnabled
      />
    );
  };

  render(): Element<*> {
    /* $FlowFixMe orientation is a maybe type - we should probably just require it */
    const landscape = this.props.screenProps.orientation.split('-')[0] === 'LANDSCAPE';
    const SelectedTabView = landscape ? TabViewVertical : TabView;
    const tabBarPosition = landscape ? 'top' : 'bottom'; // 'top' is same as 'left' in vertical tabs
    const initialLayout = { width: 600, height: 400 };
    const sceneMap = SceneMap(this.state.scenes);
    return (
      <SelectedTabView
        initialLayout={initialLayout}
        renderTabBar={this._renderTabs}
        style={styles.container}
        navigationState={this.state.tabState}
        renderScene={sceneMap}
        onIndexChange={this._handleIndexChange}
        swipeEnabled={Platform.OS !== 'web'}
        tabBarPosition={tabBarPosition}
      />
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Colors.lightGrayBackground
  },
  tabbar: {
    backgroundColor: Colors.lightBlue
  },
  tab: {
    width: 110,
    height: 80 + (getBottomSpace() / 3)
  },
  icon: {
    backgroundColor: Colors.transparent,
    color: Colors.dhsWhite
  },
  indicator: {
    width: 110,
    height: 80 + (getBottomSpace() / 3),
    backgroundColor: Colors.dhsOffWhite
  },
  label: {
    textAlign: 'center',
    fontSize: 12,
    fontFamily: 'Source Sans Pro',
    paddingTop: 5,
    color: Colors.dhsWhite,
    backgroundColor: Colors.transparent
  }
});

const mapDispatchToProps: ReduxStorePropsType = {
  updateLastActivity: actions.appGlobal.updateLastActivity
};

export {
  financeTitle,
  performanceTitle,
  TabsScreenIso
};

export default connect(null, mapDispatchToProps)(TabsScreenIso);

DaKaZ avatar Apr 30 '19 22:04 DaKaZ