expo / ex-navigation

Route-centric navigation for React Native
997 stars 201 forks source link

How to reset stack on Tabnavigation #126

Open Isarstyle opened 8 years ago

Isarstyle commented 8 years ago

I want to reset the stack if the user navigates by using the Tabbar, how is this possible? I know how to reset the stack manually, is there maybe an event thats fired on tapping a tabbar icon? Did not find anything in the Documentation. Thank you.

slicejunk commented 8 years ago

here is my solution, a better one would be still appreciated:

<TabItem onPress={onPress} .../>
            function onPress(tabItemOnPress: Function, event: Object): void {
              tabItemOnPress();
              let {navigation} = this.props
              // when clicked on a tab we want to start from the initial route
              navigation.performAction(({tabs, stacks}: {tabs: Function, stacks: Function}): void => {
                if (navigation.navigationState.navigators[tab.id] != null) {
                  stacks(tab.id).popToTop(tab.id);
                }
              });
            }
MossP commented 8 years ago

@slicejunk Sorry if I'm missing something obvious. But where do you implement this? {navigation} is undefined for me when I attempt this.

jbkaplan commented 8 years ago

@MossP The way I implemented this is:

onPress(tabItemOnPress, event) {
    tabItemOnPress();
    this.props.navigation.performAction(({ tabs, stacks }) => {
      const { currentNavigatorUID } = this.props.navigation.navigationState;
      // Reset route stack if scene is not on initial <TabNavigationItem> route
      // In my case I used an id of "main" in my <TabNavigation>
      if (this.props.navigation.navigationState.currentNavigatorUID !== 'main') {
        stacks(currentNavigatorUID).popToTop(currentNavigatorUID);
      }
    });
  }

  ....
  // My TabNavigation Implementation
  <TabNavigation id="main" initialTab="home">
    <TabNavigationItem id="home" onPress={this.onPress}>
      <StackNavigation navigatorUID="home" initialRoute={Router.getRoute('home')} />
    </TabNavigationItem>
  </TabNavigation>

Per docs, I believe navigatorUID must be defined for this to function properly. I'm also using this connected with Redux, not sure if that will change how others props appear.

I think the way this works is that on the first TabItemPress the currentNavigatorUID refers to the main TabNavigation, and on press again it hits the actual tab item so then I wanted it to jump back to the initialRoute if it was not there already. This is similar to how other mobile apps function with a TabBar like Instagram.

MossP commented 8 years ago

Thanks for the info @jbkaplan. The problem I'm facing is that the navigator is not available 'outside' of the navigator itself (i.e. this.prop.navigator is undefined).

MossP commented 8 years ago

Forgot to say. I'm not using redux, so that may be the reason. I'm not sure.

MossP commented 8 years ago

Ah, it appears that I can simply access the navigation by assigning a reference to the <TabNavigation/> item then calling .context.navigation on the ref to get access. This then enables me to use the code posted to reset tabs. Thanks.

slicejunk commented 8 years ago

@MossP here is the whole code:


  <NavigationProvider
          router={router}
          defaultRouteConfig={defaultRouteConfig}
        >
          <TabNavigation
            id="main"
            navigatorUID="main"
            initialTab={initialTab}
            ref="tabNavigation"
          >
          {tabs.map((tab: Object): React.Element<*> => {
            function onPress(tabItemOnPress: Function, event: Object): void {
              tabItemOnPress();
              let {navigation} = this.props; 
              // when clicked on a tab we want to start from the initial route
              navigation.performAction(({tabs, stacks}: {tabs: Function, stacks: Function}): void => {
                if (navigation.navigationState.navigators[tab.id] != null) {
                  stacks(tab.id).popToTop(tab.id);
                }
              });

            }

            return (
              <TabItem
                key={`tab-${tab.id}`}
                id={tab.id}
                selectedStyle={styles.selectedTab}
                title={tab.title}
                onPress={onPress}
              >
              <StackNavigation
                id={tab.id}
                navigatorUID={tab.id}
                initialRoute={router.getRoute(tab.id)}
                />
            </TabItem>
          );
          }
          )}
          </TabNavigation>
        </NavigationProvider>
MossP commented 8 years ago

Ah. I didn't realise that the function could be added in there. Thanks @slicejunk 👍

JonasWho commented 7 years ago

I have a similar problem. I have a login component before the tabs. When the user logs in, I do navigator.replace(Router.getRoute('tabs')) This works fine. But on logout, how do I reset back to before the tabs component was loaded? If I do navigator.replace(Router.getRoute('login')) inside one of the tabs, then I'm still stuck inside the tabs?

joeferraro commented 7 years ago

@JonasWho use a StackNavigation in your root component and set its initial route to login. In your root component's componentDidUpdate, look for state changes indicating the user has logged in / out and call rootNavigator.immediatelyResetStack([Router.getRoute('tabs')], 0) or rootNavigator.replace('login') accordingly. Here's an example.

JonasWho commented 7 years ago

@joeferraro thanks a lot, just needed to wrap my head around the multiple StackNavigations that I have. Your solution works fine :)

tpnguyen91 commented 7 years ago

@joeferraro thanks a lot, I got an error is duplicate navigationBar, it take a lot of time, you save me. many thanks for your supporting 👍