Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Solution] Reset to specific tab screen in a nested navigator #1715

Closed
opp100 opened this issue May 29, 2017 · 37 comments
Closed

[Solution] Reset to specific tab screen in a nested navigator #1715

opp100 opened this issue May 29, 2017 · 37 comments
Labels

Comments

@opp100
Copy link

opp100 commented May 29, 2017

Hey all,

Currently, I have using the both the Tab and stack navigator.

I have using the Tab as the main navigator, and few stack as the sub screen.

There are 4 tabs in main Navigator, the 1st tab is the initialised screen.

My current structure:

tabNavigation
  |
  tab1
  |
  tab2

StackNavigation
  |
  tabNavigation
  |
  stack 1
  |
  stack 2

How can I reset to the 2nd tab in its child stack screen? The reset to main navigator alway direct to the initialised screen.

    //navigate is working but it works like append a new stack page
    const navigateAction = NavigationActions.navigate({
      routeName: 'TabNavigator',
      action: NavigationActions.navigate({routeName: 'TabSecondScreen'})
    })
    //reset is not working, it always goes to 1st screen in the tab bar navigator
    const resetAction = NavigationActions.reset({
      index: 0,
      actions: [navigateAction]
    })
    this.props.navigation.dispatch(resetAction)

Also, I tried navigate to this specific tab screen, it works probably, but I can't rest the stack from this screen. If there are any method to drop all the previous stack also can solve my problem.

Solution

Stupid solution, but it working if you have same structure as mine. I have using AsyncStorage, thus it works without redux/flux.

//stack.js
//when you do redirection
//redirect to tab1 in tabNavigation
  ...
  toTab1() {
    var nextJump = 'tab2'; //next jump router name
    if (nextJump != null) {
      AsyncStorage.setItem('@nextJump', nextJump); //store router name to storage
    }
    const resetAction = NavigationActions.reset({
      index: 0,
      actions: [NavigationActions.navigate({routeName: 'tabNavigation'})]
    })
     this.props.navigation.dispatch(resetAction);  //do reset here
  }
  ...


//tab1.js
//get the router name in storage and jump to other tab
  ...
  componentWillMount() {
    ...
     AsyncStorage.getItem('@nextJump').then((routeName) => {
          AsyncStorage.removeItem('@nextJump').then(() => {
             const navigateAction = NavigationActions.navigate(
                  {
                      routeName, 
                      params:{'some params'}
                   });
              this.props.navigation.dispatch(navigateAction); 
          });
    });
    ...
  }
  ...

Your Environment

software version
react-navigation 1.0.0-beta.11
react-native 0.44
@farahty
Copy link

farahty commented May 29, 2017

interested , looking for someone to answer

@opp100
Copy link
Author

opp100 commented Jun 1, 2017

@pvsong do you mean put the state of tab 2nd screen into storage or redux? But how can I reset to this screen?

@lngsx
Copy link

lngsx commented Jun 3, 2017

+1

@i6mi6
Copy link

i6mi6 commented Jun 4, 2017

It would be fantastic if someone knew

@opp100
Copy link
Author

opp100 commented Jun 13, 2017

@pvsong Hey bro, I cannot figure out the solution, can you demonstrate some code for the navigate part? Thanks a lot.

@ghost
Copy link

ghost commented Jun 20, 2017

I've been pulling my hair out for months on how to get reset to work in this exact situation, some examples from those who made it work would be great. I'm trying to get this to work on a basic app, no redux or anything.

@lukem512
Copy link

Imagine you have the following set up:

// OuterNavigator.js
import { StackNavigator } from 'react-navigation';

import { headerMode, navigationOptions } from '../config';
import SubNavigator from './SubNavigator';
import ParentView from '../../views/OuterView';

const OuterNavigator = StackNavigator(
  {
    Outer: { screen: OuterView },
    Inner: { screen: SubNavigator },
  },
  {
    headerMode,
    navigationOptions,
    initialRouteName: 'Outer',
  }
);

export default OuterNavigator;
// SubNavigator.js
import { StackNavigator } from 'react-navigation';
import { headerMode, navigationOptions } from '../../routes/config';
import InnerViewA from '../../views/InnerViewA';
import InnerViewB from '../../views/InnerViewB';

const SubNavigator = StackNavigator(
  {
    ViewA: { screen: InnerViewA },
    ViewB: { screen: InnerViewB },
  },
  {
    headerMode,
    navigationOptions,
    initialRouteName: 'ViewA',
  }
);

export default SubNavigator;

Let's say that you want to navigate to Outer from within Inner, specifically from ViewB.

Simply using a this.props.navigate('Outer', params) will work but that doesn't allow for manipulation of the navigator stack.

To use NavigationActions.reset you must perform the dispatch using a navigation prop passed to a component in the OuterNavigator. This can be achieved by passing it as a navigation parameter.

// OuterView.js
export default class OuterView extends React.Component {
  render() {
    const navigation = this.props.navigation;
    return (
       <View
        <Button onPress={() => navigation.navigate('Inner', { parentNavigation: navigation })}>
          <Label>Go to Inner</Label>
        </Button>
      </View>
    );
  }
}
// InnerViewB.js
export default class InnerViewB extends React.Component {
  render() {
    const parentNavigation = this.props.navigation.state.params.parentNavigation;
    return (
       <View
        <Button onPress={() => parentNavigation.dispatch(
          NavigationActions.reset({
            key: null,
            index: 0,
            actions: [
              NavigationActions.navigate({
                routeName: 'Outer',
              }),
            ],
          })
        )}>
          <Label>Go to Inner</Label>
        </Button>
      </View>
    );
  }
}

@opp100
Copy link
Author

opp100 commented Jul 10, 2017

@lukem512 Hey bro, reset to outerView is working probably. But the current issue is how to reset to the subview of outerView.

@lukem512
Copy link

lukem512 commented Jul 11, 2017

By a subview do you mean OuterB (where the initial transition to the Inner navigator was done via OuterA?

const OuterNavigator = StackNavigator(
  {
    OuterA: { screen: OuterView },
    OuterB: { screen: AnotherOuterView },
    Inner: { screen: SubNavigator },
  },
  {
    headerMode,
    navigationOptions,
    initialRouteName: 'OuterA',
  }
);

@opp100
Copy link
Author

opp100 commented Jul 11, 2017

@lukem512 Hey bro, the view structure is

const OuterNavigator = TabNavigator(
  {
    OuterViewA: { screen: OuterViewA },
    OuterViewB: { screen: OuterViewB },
  },
  {
    headerMode,
    navigationOptions,
    initialRouteName: 'OuterA',
  }
);

const InnerNavigator = StackNavigator(
  {
    Home: { screen: OuterNavigator },
    InnerViewA: { screen: InnerViewA },
    InnerViewB: { screen: InnerViewB },
  },
  {
    headerMode,
    navigationOptions,
    initialRouteName: 'OuterA',
  }
);

Can it reset from innerViewA / innerViewB reset to OuterViewB.

@lukem512
Copy link

If you pass the navigation object from OuterViewX when navigating to one of the InnerView screens, then you can use that to navigate to any of the routes in OuterNavigator.

@opp100 opp100 changed the title How reset to specific tab screen in a nested navigator [Solution] Reset to specific tab screen in a nested navigator Jul 26, 2017
@fdnhkj
Copy link

fdnhkj commented Sep 14, 2017

After facing similar issue but from deep links, I started investigating and came up with a solution.
I needed to go from an open (or closed) app to a specific tab via deep link. But as reported by some here, I was always redirected to the first tab (it seems it's due to the Stack > Tab navigators nesting).
So I customised the Stack router to use the Tab router in case of a specific received path.

I hope this helps :)

const MyTabNavigator = TabNavigator(
  {
    tabOne: { screen: TabOne, path: 'one' },
    tabTwo: { screen: TabTwo, path: 'two' },
  }
);

const MyStackNavigator = StackNavigator(
  {
    TabNavigation: { screen: MyTabNavigator, path: 'tabs' },
    Init: { screen: Init },
    Tutorial: { screen: Tutorial },
  },
  {
    initialRouteName: 'Init'
  }
);

const previousGetActionForPathAndParams =
  MyStackNavigator.router.getActionForPathAndParams;

Object.assign(MyStackNavigator.router, {
  getActionForPathAndParams(path, params) {
    const TABS_PATH = 'tabs/';
    if (path.includes(TABS_PATH)) {
      return MyTabNavigator.router.getActionForPathAndParams(
        path.split(TABS_PATH).pop(),
        params
      );
    }
    return previousGetActionForPathAndParams(path, params);
  },
});

@jacksontbryan
Copy link

@fdnhkj i do not think this solution works for the reset() nav action. I tried it. The only reasonable work around I have without a code change to react-navigation is saving something to async storage or passing an event handler function that gets called to switch tabs.

@rpopovici
Copy link

@opp100 there is an opened PR which is suppose to fix this problem #2292
You only have to pass a key to your reset command

@appwudo
Copy link

appwudo commented Sep 22, 2017

I found a temporary trick until they provide us the feature to dynamically change the initialRouteName of the TabNavigator.


const tabScreens = {
    Home: { screen: Home },
    Tab2: { screen: Tab2 },
    Tab3: { screen: Tab3 }
};

const tabConfig = {
    swipeEnabled: true,
    tabBarPosition: 'bottom',
}
const MainScreen: NavigationContainer = TabNavigator(tabScreens, tabConfig);
const MainTab2Screen: NavigationContainer = 
        TabNavigator(tabScreens, { ...tabConfig, initialRouteName: 'Tab2' });

const RouterBase: NavigationContainer =  StackNavigator({
    Root: { screen: MainScreen },
    RootTab2: { screen: MainTab2Screen },    
    SomeOtherStack: { screen: SomeOtherStack },
}, {});

So your default navigator is RouteBase that open by default Root (MainScreen). So now let's imagine you are in SomeOtherStack and want to reset to Tab2:

this.props.navigation.dispatch(
            NavigationActions.reset({
                index: 0,
                key: null,
                actions: [
                    NavigationActions.navigate({ 
                        routeName: 'RootTab2',
                    }),
                ],
            })
        );

This make the trick. I hope there will be better solution coming soon...

@chiragpurohit71085
Copy link

HI

I have following structure

Login
Register
InnerHome -- TabNavigator
-- Home
-- Chat

After login, I want my user to navigate from Login screen to InnerHome. Below is my code...

const MainTabNavigator = TabNavigator({

Blink: {
    screen: Blink,
    path: '/blink',
    navigationOptions: {
        tabBarLabel: 'Home',
        tabBarIcon: ({ tintColor, focused }) => (
            <Ionicons
                name={focused ? 'ios-home' : 'ios-home-outline'}
                size={26}
                style={{ color: tintColor }}
            />
        ),
    },

},

Changepassword: {
    screen: Changepassword,
    path: '/changepassword',
    navigationOptions: {
        title: 'Change Password',
        tabBarLabel: 'Password',
        tabBarIcon: ({ tintColor, focused }) => (
            <Ionicons
                name={focused ? 'ios-key' : 'ios-key-outline'}
                size={26}
                style={{ color: tintColor }}
            />
        ),
    },
},
Chat: {
    screen: Chat,
    path: '/chat',
    navigationOptions: {
        title: 'Chat',
        tabBarLabel: 'Chat',
        tabBarIcon: ({ tintColor, focused }) => (
            <Ionicons
                name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
                size={26}
                style={{ color: tintColor }}
            />
        ),
    },
},
Billing: {
    screen: Billing,
    path: '/billing',
    navigationOptions: {
        title: 'Billing',
        tabBarLabel: 'Billing',
        tabBarIcon: ({ tintColor, focused }) => (
            <Ionicons
                name={focused ? 'ios-card' : 'ios-card-outline'}
                size={26}
                style={{ color: tintColor }}
            />
        ),
    },
},

});

const MainStack = StackNavigator({
Home: {
screen: HomeView,
path: '/',
navigationOptions: {
title: 'Home'
},
},
Login: {
screen: Login,
path: '/login',

},
Register: {
    screen: Register,
    path: '/register',
    navigationOptions: ({ navigation }) => ({
        title: `Register`,
    }),
},
Addpost: {
    screen: Addpost,
    path: '/addpost',
},

InnerHome: { 
    screen: MainTabNavigator,
    path: '/InnerHome',
    
  },

},{

initialRouteName: 'Home',

});

Now if user is already logged in , I want user to directly redirect from Home screen to InnerHome

I have done something like this...

const redirectWelcome = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'InnerHome' })
]
})

componentDidMount() {

		//this.props.navigation.navigate('InnerHome');
	
	alert("test");



if(user_loggedin)
{
	this.props.navigation.dispatch(redirectWelcome);
}

}

.....

but this is not working... (I have observed componentDidMount is calling infinite time of this screen only)

Please help me in this.

@deepaksasken
Copy link

Any fix for this issue ?

@JorgeCeja
Copy link

JorgeCeja commented Nov 23, 2017

DISCLAMER: This is a very hacky way but it worked for me:

onPress={() =>
Promise.all([
  navigation.dispatch(
    NavigationActions.reset({
      index: 0,
      // TabNav is a TabNavigator nested in a StackNavigator
      actions: [NavigationActions.navigate({ routeName: 'TabNav' })]
    })
  )
]).then(() => navigation.navigate('specificScreen'))
}

But it might lead to a better temporary solution. Hope it helps

@deepaksasken
Copy link

@JorgeCeja Thanks a ton, this hacky thing worked for me. :)

@wkoutre
Copy link

wkoutre commented Dec 14, 2017

@appwudo Thanks for bringing me back to sanity. I spent a couple hours trying to do this without having to have multiple screen tabScreens object but... just couldn't make it happen.

@yuoppp
Copy link

yuoppp commented Dec 26, 2017

@JorgeCeja thanks! Saved my the whole of my life

@JulianKingman
Copy link

@JorgeCeja navigation actions are synchronous, so you don't need promises, this should work the same way:

navigation.dispatch(
    NavigationActions.reset({
      index: 0,
      // TabNav is a TabNavigator nested in a StackNavigator
      actions: [NavigationActions.navigate({ routeName: 'TabNav' })]
    })
);
navigation.navigate('specificScreen'))

@slorber
Copy link
Member

slorber commented Feb 1, 2018

I can confirm dispatching 2 actions synchronously work fine.

export const navigateToRootTab = (navigation,tabName) => {
  const actions = [
    NavigationActions.reset({
      index: 0,
      actions: [
        NavigationActions.navigate({
          routeName: "Root",
        }),
      ]
    }),
    NavigationActions.navigate({
      routeName: tabName,
    })
  ];
  actions.forEach(navigation.dispatch);
};

Yet I would also have expected the reset navigation sub-action to work and being able to set the navigation in expected state with a single action.

@brentvatne
Copy link
Member

brentvatne commented Feb 10, 2018

this seems popular, can someone submit a guide to the website explaining how to do it and the use cases? http://github.com/react-navigation/website

@slorber
Copy link
Member

slorber commented Feb 13, 2018

@brentvatne I'd add this to documentation but it would be great to know first if this is the indented behavior.

For me, the following not working looks like a bug:

NavigationActions.reset({
  index: 0,
  actions: [
    NavigationActions.navigate({
      routeName: "Root", // StackNav root, whose screen is a TabNav
      action: NavigationActions.navigate({
        routeName: tabName, // This does not work, the TabNab is always reset to its initialRouteName
      })
    }),
  ]
});

if this is confirmed to be a bug, I think we should work on resolution, instead of documenting a temporary workaround.

So, can someone confirm it's a bug?

@JulianKingman
Copy link

Agreed. Unless nested actions are being done away with, I think this is a hack/workaround.

@wkoutre
Copy link

wkoutre commented Feb 13, 2018

It's definitely a hack/workaround, but not really that convoluted.

Here's how I'm handling it (credit to @appwudo ) with success (aside from currently-mounted screens quickly animating as the resetAction is dispatched... an issue independent of this):

// function to create the tabConfig based on platform (iOS/Android) and the preferred initialRoute
const createTabConfig = initialRouteName =>
  Platform.OS === "ios"
    ? {
        animationEnabled: false,
        initialRouteName,
        lazy: true,
        tabBarOptions: {
          activeTintColor: Colors.black,
          style: {
            backgroundColor: Colors.white
          },
          showLabel: false
        },
        navigationOptions: {
          headerLeft: null,
          gesturesEnabled: false
        }
      }
    : {
        animationEnabled: false,
        initialRouteName,
        lazy: true,
        tabBarOptions: {
          activeTintColor: Colors.black,
          style: {
            backgroundColor: Colors.white
          },
          showLabel: false
        },
        tabBarComponent: TabBarBottom,
        tabBarPosition: "bottom",
        swipeEnabled: false,
        enimationEnabled: false,
        backBehavior: "none",
        navigationOptions: {
          headerLeft: null,
          gesturesEnabled: false,
          headerTitleStyle: { alignSelf: "center" }
        }
      };

const tabScreens = {
    ...your tab screens
}

const HomeNavigation = TabNavigator(tabScreens, createTabConfig(screenYouWantToBeFirst));
const HomeNavWithThirdTabActive = TabNavigator(tabScreens, createTabConfig("thirdTabName"));

export { HomeNavigation, HomeNavWithThirdTabActive }

@JulianKingman
Copy link

@wkoutre doesn't that create a copy of the whole stack, doubling your rendered elements?

@wkoutre
Copy link

wkoutre commented Feb 20, 2018

@JulianKingman No -- at least not to my knowledge, anyway. I've set lazy to true so screens in the TabNavigator are loaded as they're mounted, not when the TabNavigator is navigated to. On my RootNav, I have screens with HomeNavigation and HomeNavThirdTab, navigating to one or the other -- in context with what the User is doing -- after performing a reset action.

...
    Home: {
      screen: HomeNavigation,
      navigationOptions: {
        gesturesEnabled: false
      }
    },
    HomeThirdTab: {
      screen: HomeNavWithThirdTabActive,
      navigationOptions: {
        gesturesEnabled: false
      }
    },
...

@brentvatne
Copy link
Member

this is a pretty long issue but if I understand it correctly then this rfc should solve it when implemented: react-navigation/rfcs#25

@slorber
Copy link
Member

slorber commented Mar 7, 2018

@brentvatne I've read the RFC but not sure to understand how it is related.

Will this work after the RFC is implemented?

NavigationActions.reset({
  index: 0,
  actions: [
    NavigationActions.navigate({
      routeName: "Root",
      action: NavigationActions.navigate({
        routeName: tabName,
      })
    }),
  ]
});

@brentvatne
Copy link
Member

this.props.navigation.navigate('nameOfFirstRouteInThatTab')

@slorber
Copy link
Member

slorber commented Mar 11, 2018

Thanks @brentvatne . So, if this is possible, what's the purpose of passing an extra navigate action like in my code above? In which case would you use this navigateAction sub-action?

@anam-hossain
Copy link

@slorber, @brentvatne I am using the following code:

const HomeStack = StackNavigator(
  {
    Home: { screen: HomeScreen },
    Category: { screen: CategoryScreen },
    Details: { screen: DetailsScreen }
  },
  {
    initialRouteName: "Home"
  }
);

const FavouriteStack = StackNavigator({
  Favourite: { screen: FavouriteScreen }
});

const SettingsStack = StackNavigator({
  Settings: { screen: SettingsScreen }
});

export default TabNavigator(
  {
    HomeTab: { screen: HomeStack },
    FavouriteTab: { screen: FavouriteStack },
    SettingsTab: { screen: SettingsStack }
  },
  {
    tabBarPosition: "bottom",
    tabBarComponent: props => {
      return (
        <Footer>
          <FooterTab>
            <Button
              vertical
              active={props.navigationState.index === 0}
              onPress={() => props.navigation.navigate("HomeTab")}
            >
              <Icon name="home" />
              <Text>Home</Text>
            </Button>
            <Button
              vertical
              active={props.navigationState.index === 1}
              onPress={() => props.navigation.navigate("FavouriteTab")}
            >
              <Icon name="star" />
              <Text>Favourite</Text>
            </Button>
            <Button
              vertical
              active={props.navigationState.index === 2}
              onPress={() => props.navigation.navigate("SettingsTab")}
            >
              <Icon name="settings" />
              <Text>Settings</Text>
            </Button>
          </FooterTab>
        </Footer>
      );
    },
  }
);

Where do I put the reset navigation stack code?

@slorber
Copy link
Member

slorber commented Apr 16, 2018

It depends if you use stateful navigator or not (ie using Redux).

You can dispatch a navigation action if you use Redux, or you can wrap your navigator in a class comp, get a ref on it, and call dispatch on the navigator component instance

@anam-hossain
Copy link

anam-hossain commented Apr 16, 2018

you can wrap your navigator in a class comp, get a ref on it, and call dispatch on the navigator component instance

I am not using Redux. When someone presses the home tab, it will reset the stack. I am new to react and react native. Please guide me.

@slorber
Copy link
Member

slorber commented Apr 16, 2018

class StackNavigationWrapper extends React.Component {
  componentDidMount() {
    this.stackNavigationRef.dispatch(NavigationActions.reset({...}))
  }
  render() {
    return (
      <MyStackNavigator ref={c => this.stackNavigationRef = c}/>
    );
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests