5

Here is my project file hierarchy

RootTabNavigator
    | AuthStackNavigator // I want to go back to this navigator
          | AuthoScreen
    | Welcome Screen
    | MainTabNavigator // I want to reset MainTabNavigator 
          | FeedStacknavigator
                   | Screen A
          | OtherStackNavigatorOne
                   | Screen E
          | OtherStackNavigatorTwo
                   | Screen D
          | MenuStackNavigator 
                   | Menuo <-I'm here and want to reset to 'MainTabNavigator' 
                             and go BACK to 'AuthScreen'
           | Screen B
                   | Screen C

Problem

The user is on Menuo Screen under MenuStackNavigator and MainTabNavigator.

If user doesn't have a token (when user logs out), user goes back to the Auth Screen.

But at the same time I want to RESET MainTabNavigator. You can unmount , perform NavigationActions.init() or whatever you can. I prefer NavigationActions.init()

I just want to set MainTabNavigator to its very first time.

Code

if there is no token, I go back to Auth Screen (This is working)

This code if the part of Menuo Screen

componentWillReceiveProps(nextProps) {
    if ( nextProps.token == undefined || _.isNil(nextProps.token) ) {
      const backAction = NavigationActions.back({
        key: null
      })
      nextProps.navigation.dispatch(backAction);
      ...

(Question) How can we reset MainTabNavigator including child StackNavigators?

MainTabNavigator.js

export default TabNavigator(
    {
        Feed: {
          screen: FeedStacknavigator,
        },
        OtherOne: {
          screen: OtherStackNavigatorOne,
        }
        ...
    }, {
        navigationOptions: ({navigation}) => ){
            header: null,
        tabBarIcon: ({focused}) => ...
        ...
    }

Possible Solution

I can maybe change MainTabNavigator from function to class and deal with resetting TabNavigator there. (I'm not sure).

This time, I need a concrete working example. I've been reading doc and applying to my app but I couldn't solve this.

Please let me know if anything is unclear.

UPDATE

const RootTabNavigator = TabNavigator ({
    Auth: {
      screen: AuthStackNavigator,
    },
    Welcome: {
      screen: WelcomeScreen,
    },
    Main: {
      screen: MainTabNavigator,
    },
  }, {
    navigationOptions: () => ({
     ...
  }
);

export default class RootNavigator extends React.Component {
  componentDidMount() {
    this._notificationSubscription = this._registerForPushNotifications();
  }
merry-go-round
  • 4,533
  • 10
  • 54
  • 102
  • I think the quick solution will be reseting the MainNavigator. Everything inside it will be unmonted automatically. Like Login -> Profile-> Logout then back to Login. Is this what you are trying to solve.. – Ashwin Mothilal Dec 31 '17 at 14:07
  • Yes it's exactly what I was looking for. Can I reset AFTER back action?? If this works, I want to take that as an answer. What information do you need more?? – merry-go-round Jan 01 '18 at 11:08
  • 1
    Suggest to use [nativebase](https://nativebase.io/) for tabs, with only `StackNavigator`. You'll have more control over tabs and easy do `reset` on it. Deep recursive structure of Navigators cause problem, every answer you got might be just a workaround. And next time it comes up again with more complex structure. – Val Jan 03 '18 at 05:26
  • There is nativebase tabs inside of Each StackNavigator (my project is big). Yeah for next time, I will use native-base Tabnavigator but I really have to rely on TabNavigator – merry-go-round Jan 03 '18 at 05:27
  • Thanks for your advice though. I appreciate that. Bring some other friends who knows react-navigation more. I really need to solve this problem for a quick... – merry-go-round Jan 03 '18 at 05:28
  • See if this example can help you https://github.com/shubhnik/redux-react-navigation-demos/tree/nestedNavigators . There are other branches too for different scenarios if needed. – Shubhnik Singh Jan 03 '18 at 05:49
  • @ShubhnikSingh I guess your are the author of that article so I'm asking question. From navigationReducer, how do you determine the action.type such as "case "@@redux/INIT", "LOGIN", "LOGOUT". We have to call the type somewhere but where do we call it? – merry-go-round Jan 03 '18 at 07:00
  • '@@redux/INIT' is dispatched by redux on the app start automatically, though you shouldn't handle that action in your reducer, I have added an update to the article. For login and logout I am dispatching the actions here https://github.com/shubhnik/redux-react-navigation-demos/blob/nestedTab/src/Components/LoginScreen.js#L25 and here https://github.com/shubhnik/redux-react-navigation-demos/blob/nestedTab/src/Components/screen2.js#L22 respectively. – Shubhnik Singh Jan 06 '18 at 07:56

3 Answers3

4

This should work in most cases:

componentWillReceiveProps(nextProps) {
    if ( nextProps.token == undefined || _.isNil(nextProps.token) ) {

        let action = NavigationActions.reset({
            index: 0,
            key: null,
            actions: [
                NavigationActions.navigate({routeName: 'Auth'})
            ]
        });

        nextProps.navigation.dispatch(action);
    }
    ...
}

Or try by enhancing your navigator with custom action:

const changeAppNavigator = Navigator => {
   const router = Navigator.router;

   const defaultGetStateForAction = router.getStateForAction;

   router.getStateForAction = (action, state) => {
       if (state && action.type === "RESET_TO_AUTH") {
          let payLoad = {
              index: 0,
              key: null,
              actions: [NavigationActions.navigate({routeName: "AuthStackNavigator"})]
          };

          return defaultGetStateForAction(NavigationActions.reset(payLoad), state);
          // or this might work for you, not sure:
          // return defaultGetStateForAction(NavigationActions.init(), state)
       }
       return defaultGetStateForAction(action, state);
  };

  return Navigator;
};

const screens = { ... }

RootTabNavigator = changeAppNavigator(TabNavigator(screens, {
  initialRouteName: ...,
  ...
}));

Then in your Menuo Screen do:

componentWillReceiveProps(nextProps) {
    if ( nextProps.token == undefined || _.isNil(nextProps.token) ) {

        nextProps.navigation.dispatch({type: "RESET_TO_AUTH"});
    ...
zarcode
  • 2,465
  • 17
  • 31
  • Oh the code is easy to read and I understand how it might work!! But my RootTabNavigator is already defined as class. Can we use same logic for my RootTabNavigator? Thanks! – merry-go-round Jan 03 '18 at 11:20
  • @JohnBaek you should be able to use it on any navigator, I have tried it in few cases, worked for me, have it in my projects... – zarcode Jan 03 '18 at 11:56
  • I'm getting undefined is not an object (evaluating '_this3._component.setNativeProps') when I try to dispatch({type: "RESET_TO_AUTH"}) on other screen(MenuScreen > ChangePasswordScreen).. – merry-go-round Jan 05 '18 at 05:02
  • Before doing `this.props.navigation.dispatch({type: "RESET_TO_AUTH"});` can you please confirm that `navigation` is present on props? – zarcode Jan 05 '18 at 06:57
  • Sure. it prints `Object { "dispatch": [Function anonymous], "goBack": [Function goBack], "navigate": [Function navigate], "setParams": [Function setParams], "state": Object { "key": "id-xxxxx578521-6", "params": undefined, "routeName": "ChangePassword", }, }` – merry-go-round Jan 05 '18 at 07:00
  • But the weird thing is that it doesn't go back to Auth Screen. It goes to MainTabNavigator. This is weird. – merry-go-round Jan 05 '18 at 07:26
  • This is very weird. I added redux props `changed` then pass it to `componenetWillReceiveProps` then I dispatched `RESET_TO_AUTH`. Then same error. – merry-go-round Jan 05 '18 at 07:43
  • printed nextProps.navigation and it prints 'Menuo' Screen. :( Why isn't it woking correctly this is so annoying – merry-go-round Jan 05 '18 at 07:44
  • https://stackoverflow.com/questions/48108205/typeerror-undefined-is-not-an-object-evaluating-this3-component-setnativepr – merry-go-round Jan 05 '18 at 07:52
  • yeah it's same thing. and MenuScreen is a parent of ChangePasswordScreen in same navigator. And I don't see your point. – merry-go-round Jan 05 '18 at 07:54
  • can we talk in this question? https://stackoverflow.com/questions/48108205/typeerror-undefined-is-not-an-object-evaluating-this3-component-setnativepr – merry-go-round Jan 05 '18 at 07:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/162571/discussion-between-john-baek-and-zarcode). – merry-go-round Jan 05 '18 at 08:04
2

What you need is to initialize the navigator to the initial state. You can do that with NavigationActions.init(). You can learn more about Navigations Actions here.

You can do this by creating a Custom Navigation Action, read more about them here.

Here's some code that would do that for you:

// First get a hold of your navigator
const navigator = ...

// Get the original handler
const defaultGetStateForAction = navigator.router.getStateForAction

// Then hook into the router handler
navigator.router.getStateForAction = (action, state) => {

  if (action.type === 'MyCompleteReset') {
     // For your custom action, reset it all
     return defaultGetStateForAction(NavigationActions.init())
  }

  // Handle all other actions with the default handler
  return defaultGetStateForAction(action, state)
}

In order to trigger your Custom Navigation Action, you have to dispatch it as follows from within your React Component:

  this.props.navigation.dispatch({
      type: "MyCompleteReset",
      index: 0
    })
idancali
  • 847
  • 5
  • 10
1

You can define custom navigation logic by extending the router. To accomplish what you want that you described in the project file hierarchy in your question you can do something like this below.

MainTabNavigator.js

...

RootTabNavigator.router.getStateForAction = (action, state) => {
  if (state && action.type === 'GoToAuthScreen') {
    return {
      ...state,
      index: 0,
    };
  }

  return RootTabNavigator.router.getStateForAction(action, state);
};

MainTabNavigator.router.getStateForAction = (action, state) => {
  if (state && action.type === 'GoToAuthScreen') {
    return {
      ...state,
      index: 0,
    };
  }

  return MainTabNavigator.router.getStateForAction(action, state);
};

MenuStackNavigator.router.getStateForAction = (action, state) => {
  if (state && action.type === 'GoToAuthScreen') {
    return {
      ...state,
      index: 0,
    };
  }

  return MenuStackNavigator.router.getStateForAction(action, state);
};

In the Menuo Screen file

componentWillReceiveProps(nextProps) {
  if ( nextProps.token == undefined || _.isNil(nextProps.token) ) {
    const goToAuthScreen = () => ({
      type: 'GoToAuthScreen',
    });

    nextProps.navigation.dispatch(goToAuthScreen);
    ...
  }
}