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 · 39 comments

Comments

Projects
None yet
@opp100

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
@nimerfarahty

This comment has been minimized.

nimerfarahty commented May 29, 2017

interested , looking for someone to answer

@pvsong

This comment has been minimized.

pvsong commented Jun 1, 2017

Save navigation main to storage or redux and use reset action.
Work any screen.

@opp100

This comment has been minimized.

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?

@luangch

This comment has been minimized.

luangch commented Jun 3, 2017

+1

@i6mi6

This comment has been minimized.

i6mi6 commented Jun 4, 2017

It would be fantastic if someone knew

@pvsong

This comment has been minimized.

pvsong commented Jun 9, 2017

@opp100 when app init on 1st screen, save "this.props.navigation" like root_navigation
and try to navigate :)

@opp100

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

lukem512 commented Jul 10, 2017

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

lukem512 commented Jul 12, 2017

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 from How reset to specific tab screen in a nested navigator to [Solution] Reset to specific tab screen in a nested navigator Jul 26, 2017

@kelset kelset added the question label Sep 12, 2017

@fdnhkj

This comment has been minimized.

Contributor

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

This comment has been minimized.

jacksontbryan commented Sep 15, 2017

@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.

@jacksontbryan jacksontbryan referenced this issue Sep 15, 2017

Closed

Renewed Path to React Navigation V1.0 #2585

11 of 11 tasks complete
@rpopovici

This comment has been minimized.

rpopovici commented Sep 15, 2017

@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

This comment has been minimized.

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

This comment has been minimized.

chiragpurohit71085 commented Sep 23, 2017

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

This comment has been minimized.

deepaksasken commented Nov 23, 2017

Any fix for this issue ?

@JorgeCeja

This comment has been minimized.

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

This comment has been minimized.

deepaksasken commented Nov 24, 2017

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

@wkoutre

This comment has been minimized.

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

This comment has been minimized.

yuoppp commented Dec 26, 2017

@JorgeCeja thanks! Saved my the whole of my life

@JulianKingman

This comment has been minimized.

JulianKingman commented Jan 21, 2018

@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

This comment has been minimized.

Contributor

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

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

This comment has been minimized.

JulianKingman commented Feb 13, 2018

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

@wkoutre

This comment has been minimized.

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

This comment has been minimized.

JulianKingman commented Feb 15, 2018

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

@wkoutre

This comment has been minimized.

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

This comment has been minimized.

Member

brentvatne commented Mar 7, 2018

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

This comment has been minimized.

Contributor

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

This comment has been minimized.

Member

brentvatne commented Mar 9, 2018

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

@slorber

This comment has been minimized.

Contributor

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

This comment has been minimized.

anam-hossain commented Apr 15, 2018

@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

This comment has been minimized.

Contributor

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

slorber commented Apr 16, 2018

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

@brentvatne brentvatne closed this Apr 30, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment