Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Navigation Experimental is unmounting Tabs

I am working on a react native app that uses Composite Experimental Navigation (CardStack + Tabs) and redux for state management. I was able to create Tab based Navigation but the issue that i am facing now is when i switch between tabs the component is unmounted and it re-render every time.

Problems

Lets say i have scrolled down several posts and when i change Tab it will start from top. (Workaround could be to store scroll position in redux state).

Here is the sample code i am using for Navigation Tabbed Experimental Navigation

like image 203
Nakib Avatar asked Aug 17 '16 23:08

Nakib


1 Answers

You must change approach for TabBar.

Basically you want Navigator per Tab, so you can have route stack for each Tab and one Navigator to contain Tabs (TabBar). You also want to set initial route stack for TabBar and jump between those routes.

It is important to understand difference between Navigator methods.

  • When you pop route, it's unmounted, and active index is moved to last one. As last one is kept in state it will be restored as it was previously rendered (most likely, it can happen props changed). Going again to popped route will rerender scene entirely (this happens to you).

  • When you push route, nothing is unmounted, new route is mounted.

  • When you jumpToIndex route, again nothing in unmounted, thus jumping
    between routes restores scenes as they were (again, if props changed scene will be rerendered).

So, I don't think this is correct:

I was able to create Tab based Navigation but the issue that i am facing now is when i switch between tabs the component is unmounted and it re-render every time.

... you unmount routes with wrong navigation actions.

Also what is different now, NavigationCardStack doesn't actual create it's own state, it is passed from outside, which gives you great flexibility. And also good thing is that you can use reducers provided by Facebook for common actions (such as push, pop, jumpToIndex; they are part of Navigation Utils).

You have full example on how to create navigationState and it reducers here, so I am not going to explain that, just going to give idea how to solve your problem.

[UPDATE] Example works now!

import React from 'react';
import { NavigationExperimental, View, Text, StyleSheet } from 'react-native';

const {
  CardStack: NavigationCardStack,
  StateUtils: NavigationStateUtils,
} = NavigationExperimental;

const style = StyleSheet.create({
  screen: {
    flex: 1,
  },
  screenTitle: {
    marginTop: 50,
    fontSize: 18,
  },
  pushNewScreenLabel: {
    marginVertical: 10,
    fontSize: 15,
    fontWeight: "bold",
  },
  goBackLabel: {
    fontSize: 15,
  },
  tabBarWrapper: {
    position: 'absolute',
    height: 50,
    bottom: 0,
    left: 0,
    right: 0,
    top: null,
    backgroundColor: 'grey',
    flexDirection: 'row',
    flex: 0,
    alignItems: 'stretch',
  },
  tabBarItem: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: 'red',
  },
});

export class TabBar extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.jumpToTab = this.jumpToTab.bind(this);

    // Create state
    this.state = {
      navigationState: {
        // Active route, will  be rendered as default
        index: 0,
        // "tab-s" represents route objects
        routes: [
          { name: 'Tab1', key: '1' },
          { name: 'Tab2', key: '2' },
          { name: 'Tab3', key: '3' },
          { name: 'Tab4', key: '4' }],
      },
    };
  }

  jumpToTab(tabIndex) {
    const navigationState = NavigationStateUtils.jumpToIndex(this.state.navigationState, tabIndex);
    this.setState({ navigationState });
  }

  renderScene({ scene }) {
    return <Tab tab={scene.route} />;
  }

  render() {
    const { navigationState } = this.state;
    return (
      <View style={style.screen}>
        <NavigationCardStack
          onNavigate={() => {}}
          navigationState={navigationState}
          renderScene={this.renderScene}
        />
        <View style={style.tabBarWrapper}>
          {navigationState.routes.map((route, index) => (
            <TabBarItem
              key={index}
              onPress={this.jumpToTab}
              title={route.name}
              index={index}
            />
            ))}
        </View>
      </View>
    );
  }
}

class TabBarItem extends React.Component {
  static propTypes = {
    title: React.PropTypes.string,
    onPress: React.PropTypes.func,
    index: React.PropTypes.number,
  }

  constructor(props, context) {
    super(props, context);
    this.onPress = this.onPress.bind(this);
  }
  onPress() {
    this.props.onPress(this.props.index);
  }
  render() {
    return (
      <Text style={style.tabBarItem} onPress={this.onPress}>
        {this.props.title}
      </Text>);
  }
}

class Tab extends React.Component {
  static propTypes = {
    tab: React.PropTypes.object,
  }

  constructor(props, context) {
    super(props, context);
    this.goBack = this.goBack.bind(this);
    this.pushRoute = this.pushRoute.bind(this);
    this.renderScene = this.renderScene.bind(this);
    this.state = {
      navigationState: {
        index: 0,
        routes: [{ key: '0' }],
      },
    };
  }

  // As in TabBar use NavigationUtils for this 2 methods
  goBack() {
    const navigationState = NavigationStateUtils.pop(this.state.navigationState);
    this.setState({ navigationState });
  }

  pushRoute(route) {
    const navigationState = NavigationStateUtils.push(this.state.navigationState, route);
    this.setState({ navigationState });
  }

  renderScene({ scene }) {
    return (
      <Screen
        goBack={this.goBack}
        goTo={this.pushRoute}
        tab={this.props.tab}
        screenKey={scene.route.key}
      />
    );
  }

  render() {
    return (
      <NavigationCardStack
        onNavigate={() => {}}
        navigationState={this.state.navigationState}
        renderScene={this.renderScene}
      />
    );
  }
}

class Screen extends React.Component {
  static propTypes = {
    goTo: React.PropTypes.func,
    goBack: React.PropTypes.func,
    screenKey: React.PropTypes.string,
    tab: React.PropTypes.object,
  }

  constructor(props, context) {
    super(props, context);
    this.nextScreen = this.nextScreen.bind(this);
  }

  nextScreen() {
    const { goTo, screenKey } = this.props;
    goTo({ key: `${parseInt(screenKey) + 1}` });
  }

  render() {
    const { tab, goBack, screenKey } = this.props;
    return (
      <View style={style.screen}>
        <Text style={style.screenTitle}>
          {`Tab ${tab.key} - Screen ${screenKey}`}
        </Text>
        <Text style={style.pushNewScreenLabel} onPress={this.nextScreen}>
          Push Screen into this Tab
        </Text>
        <Text style={style.goBackLabel} onPress={goBack}>
          Go back
        </Text>
      </View>
    );
  }
}
## TBD little more clean up..
like image 100
Mr Br Avatar answered Sep 21 '22 16:09

Mr Br