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

Problem understanding the reset action #325

Closed
jhilden opened this Issue Feb 13, 2017 · 29 comments

Comments

Projects
None yet
@jhilden
Contributor

jhilden commented Feb 13, 2017

I have the use case that I want to wipe the complete navigation history on user logout inside my react native app (with some nested navigators). It seems like the Reset action would be the way to go, and that's what I have tried, but without luck so far. My problem is that I don't really understand from the documentation how exactly the Reset action works and how it is supposed to be used. I would like to understand it and to help writing better documentation for the next developer. What exactly is the index parameter for? How does the Reset action relate to nested navigators? Any general input on that would be highly appreciated.


Concerning my concrete logout use case, here is my relevant code:

const MainNavigation = StackNavigator({
  Main: { screen: MainTabNavigation },
  Login: { screen: LoginView }
}

const MainTabNavigation = TabNavigator({
  Dashboard: { screen: DashboardView }
})

class DashboardView extends React.Component {
  logout () {
    this.props.navigation.dispatch({
      type: NavigationActions.NAVIGATE,
      routeName: 'Login',
      action: {
        type: NavigationActions.RESET,
        index: 0,
        actions: [{type: NavigationActions.NAVIGATE, routeName: 'Login'}]
      }
    })
  }
}

This does navigate to the Login view correctly, however it does not clear the navigation history, so when the user clicks e.g. the back button she ends up back on the logged out Dashboard.

@zenyr

This comment has been minimized.

zenyr commented Feb 14, 2017

import { NavigationActions } from 'react-navigation';
/* ... */  
    const resetAction = NavigationActions.reset({
      index: 0,
      actions: [NavigationActions.navigate({ routeName: 'Login' })],
    });

    this.props.navigation.dispatch(resetAction);

This did the trick for me.
If you are trying to reset every single history then index : 0 would serve you well enough, but it is possible that you are trying to force-reset the user to some view but want to "slip in" arbitrary history for the user, you may put index: 1 with actions:[ {routeName:'SomeScreen',params:{trapCard:'activated'}}, {routeName:'PressBackToWin$$$'} ]. (note: pseudo code)

I think it is also possible to spawn a route action with [ NavigationActions.navigate('SomeScreen',{trapCard:'activated'}), NavigationActions.navigate('PressBackToWin$$$')

@jhilden

This comment has been minimized.

Contributor

jhilden commented Feb 14, 2017

thanks for your input @zenyr

I actually realized that the version of the reset code I posted earlier (dispatch({type: NavigationActions.RESET, index: 0, actions: [{type: NavigationActions.NAVIGATE, routeName: 'Login'}]}), which is just a different syntax of the code you suggested) did not work for me because the reset action is handled by the wrong navigator which does not know about the Login route (=> There is not route defined for key Login). I think #199 is talking about that.

I updated the code in the issue description above so it does a navigate with a nested reset action, like suggested here #199 (comment) but still I have no reseted navigation history afterwards. Instead the history looks like this in the console output:

{
  index: 2,
  routes: [
    {key: "Init", routeName: "Login"},
    {index: 1, key: "id-1487070472668-0", routeName: "Home", type: "Navigation/NAVIGATE"},
    {key: "id-1487070472668-1", routeName: "Login", type: "Navigation/NAVIGATE"}
  ]
}
@zenyr

This comment has been minimized.

zenyr commented Feb 14, 2017

One caveat that had struck me was, that when you're nesting Navigators, you should directly connect routers.

I once tried to something like this

Root.js

StackNavigator({
  Intro: {screen: SceneIntro}, // suppose intro was mere a <View />, nothing special
  Main: {screen: SceneMain} // this is the main one with tabs and such
})

main.js

const MyNestedTabNav = TabNavigator({
  Inner: {...},
  Views: {...},
  AndSuch: {...}
})
export default class SceneMain extends React.Component {
 render(){
   <MyNestedTabNav /> // Seems legit... ?
 }
}

Only to find out that "SceneMain" HOC blocked the access from Root.js <Nav /> into Main.js <Nav />. I could not navigate back to Intro from 'AndSuch' view, as MyNestedTabNav does not delegate its navigation to the Root.js. (while it could jump between 'Inner' and 'Views' without too much problem)

After plugging <Navigator />s directly (thus connecting .routers) I could props.navigate('AnyView') between anywhere.

There are many reasons that the config could go wrong but basically this is my 1 cent. I think I am quite familiar with reactjs for web but it took a long time for me to find out that I had made obvious errors.

Edit: After reading your mentioned thread I may have went tangent to the actual issue. 😓

@jhilden

This comment has been minimized.

Contributor

jhilden commented Feb 14, 2017

I'm "directly" connecting my nested navigators (no render() in between), so that can't be the issue.

@ElliottJRo

This comment has been minimized.

Contributor

ElliottJRo commented Feb 14, 2017

Hey all,

So my comment on 199 was wrong and further testing resulted in strange behaviour. I think the way to go for the Reset action is to create a custom router and implement a different reset action handler. Or reading up on the [Redux integration] (https://reactnavigation.org/docs/guides/redux) makes me think you can

I was unable to find a way to get the TargetView to handle the Reset action. Some clues if you can follow what happens to a nested action here

edit: maybe related -> link

@Kerumen

This comment has been minimized.

Contributor

Kerumen commented Mar 14, 2017

@ElliottJRo Did you manage to get this working since 28 days now? I'm stuck with this problem too and I can't find a solution..

@ElliottJRo

This comment has been minimized.

Contributor

ElliottJRo commented Mar 14, 2017

@Kerumen only thing I could do atm is modify the Router's getStateForAction to return a clean route state. related: https://reactnavigation.org/docs/routers/api

@Kerumen

This comment has been minimized.

Contributor

Kerumen commented Mar 14, 2017

Did you use Redux for that?

@jhilden

This comment has been minimized.

Contributor

jhilden commented Mar 15, 2017

FYI: I ended up structuring my navigators differently to solve my original problem with resetting routes on logout. This is what I have now:

class RootComponent extends React.Component {
  render () {
    if (userLoggedIn) {
      return (<MainNavigator />)
    } else {
      return (<LoginNavigator />)
    }
  }
}

So when the user is logged out the MainNavigator completely disappears and I don't have an issue anymore with the back button leading to a blank Homescreen.

This doesn't really solve the original issues I had with the reset action, but if it is up to me this issue could be closed anyway.

@ElliottJRo

This comment has been minimized.

Contributor

ElliottJRo commented Mar 15, 2017

I think what jhilden did makes the most sense, maybe we are not supposed to nest navigators so deep and its okay not to throw all views into a monolithic routing system

@sergeylaptev

This comment has been minimized.

sergeylaptev commented Apr 17, 2017

This looks like a horrible workaround, would be great to implement global routeName navigation. As any major web routing libs do (e.g. react-router). You just specify the name of route/scene it might be on arbitrary depth of tree. E.g. I use redux-saga and when some action occured, I want to navigate to second-level scene (RootNavigator -> MainNavigator -> <Scene>). Any thoughts about this?

@spidergears

This comment has been minimized.

spidergears commented Apr 23, 2017

@jhilden That seems to be a work around right now.
I had had a similar requirement to separate login flow from the mainApp,
I ended up using react-native-router-flux, a complicated but really flexible router.

<Router>
      //login nav bucket
      <Scene key='loginApp' hideNavBar={true} >
        <Scene key='login' component={Header} />
      </Scene>

      //main app nav bucket
      <Scene key='mainApp' type={ActionConst.RESET}>
        <Scene key='labProfile' component={Profile} title='Profile' />
      </Scene>
    </Router>

react-native-router-flux allows grouping routes into buckets, which won't bother other buckets and can function independently.

I am not sure if we have similar functionality available in react-navigation.
I would love to switch back to react-navigation for the simplicity it offers.
If anyone is aware please tag.

@jhilden

This comment has been minimized.

Contributor

jhilden commented Apr 23, 2017

@spidergears actually, it has been working pretty well so far. It was not obvious to figure this out in the first place but after it was implemented this way it did not create any further issues and has been very comprehensible to everybody in the team.

But since this is a very frequent use case and the solution non-obvious, I think it would be very good to have some documentation pointing library users down this way.

@scbrady

This comment has been minimized.

Contributor

scbrady commented Apr 27, 2017

@jhilden I had this same problem where I needed to reset the stack to the root of the lowest stack navigator. I made this PR (#789) that lets you specify the key of the navigator that you want to reset. You can now reset the root navigator by passing the key null

NavigationActions.reset({ index: 0, actions: [{type: NavigationActions.NAVIGATE, routeName: 'Login'}], key: null })

It looks like this PR just landed in the newest beta version (v1.0.0-beta.9)

@zakster12

This comment has been minimized.

Contributor

zakster12 commented May 4, 2017

@scbrady Thank you for your fix with the key: null. It works for me for resetting the StackNavigator's history but TabNavigator's history is not resetted (child of the StackNavigator in my example):

I have the following structure.


const routeConfiguration = {
	Login: { screen: Login },
	Home: { screen: TabBar },
};

const stackNavigatorConfiguration = {
	headerMode: 'screen',
	navigationOptions: {
		header: { visible: false }
	}
};

export const RootNav = StackNavigator(routeConfiguration, stackNavigatorConfiguration);

My TabBar where each Tab has it's own StackNavigator.

const routeConfiguration = {
	TabOneNavigation: { screen: TabOneNavigation },
	TabTwoNavigation: { screen: TabTwoNavigation },
	TabThreeNavigation: { screen: TabThreeNavigation },
};

const tabBarConfiguration = {
	tabBarOptions: {
		activeTintColor: 'white',
		inactiveTintColor: 'lightgray',
		labelStyle: {
			fontSize: 10,
			fontFamily: Fonts.book
		},
		style: {
			backgroundColor: Colors.greenLightGradient,
			borderTopWidth: 1,
			borderTopColor: Colors.tabGreenLine
		},
	}
};

export const TabBar = TabNavigator(routeConfiguration, tabBarConfiguration);

When the app first load It goes to Login screen. After successful login I use actionTypes.TO_HOME to go to Home. There I have 3 tabs (Feed, Suggestion, Profile). Inside Profile tab I have Log Out button pressing on which I reset the navigation history and go to Login Again (so far so good). But when I login again and go to Home, it shows the first tab for a second and navigates me to the last one (Profile) which looks like the TabNavigator's history is not resetted. Should I do something special so that the TabNavigator's history is also resetted?

This is my reducer for resetting the history and going to the Login screen:

export const navReducer = (state = initialState, action = {}) => {
	let nextState;
	switch (action.type) {
		case actionTypes.TO_LOGIN:
			nextState = RootNav.router.getStateForAction(
				NavigationActions.reset({
					index: 0,
					actions: [NavigationActions.navigate({ type: NavigationActions.NAVIGATE, routeName: actionTypes.TO_LOGIN })],
					key: null
				}), state);
			break;

		case actionTypes.TO_HOME:
			nextState = RootNav.router.getStateForAction(
				NavigationActions.reset({
					index: 0,
					actions: [NavigationActions.navigate({ type: NavigationActions.NAVIGATE, routeName: actionTypes.TO_HOME })],
				}), state);
			break;

		default:
			nextState = RootNav.router.getStateForAction(action, state);
			break;
	}

	return nextState || state;
};

Thank you in advanced!

@scbrady

This comment has been minimized.

Contributor

scbrady commented May 4, 2017

@zakster12 That's odd. I do the same thing in my app and it works. When you reset the root navigator it should pop the TabNavigator off of the stack, so by default it would be reset as well. When you call the TO_LOGIN action, does it replace the contents of the top level routes array with the login screen in Redux?

The only difference that I see between my app that works and yours is that my login screen is a StackNavigator, so instead of my root StackNavigator having a Scene and a TabNavigator, it has a StackNavigator and TabNavigator. I'm not sure why that would break things, but it might be worth a shot.

@zakster12

This comment has been minimized.

Contributor

zakster12 commented May 5, 2017

@scbrady Thank you for your help. I nested my login screen in another StackNavigator but that didn't help. I saw that when I reset the root stack navigator with (TO_LOGIN) the index of TabNavigator is staying to 2 (checked with redux-logger). As a workaround I use another action which sets the index of the TabNavigator to 0 and then going to Login. It's not the right thing but works for now.

@laclance

This comment has been minimized.

laclance commented May 9, 2017

This works for me however its shows the goBack animations for every screen in between

@diegorodriguesvieira

This comment has been minimized.

diegorodriguesvieira commented May 15, 2017

@laclance did you solve this animation problem?

@CWolfAnderson

This comment has been minimized.

CWolfAnderson commented Aug 3, 2017

@diegorodriguesvieira did you solve the animation problem?

@dan-kez

This comment has been minimized.

dan-kez commented Aug 10, 2017

@laclance / @diegorodriguesvieira / @CWolfAnderson

Any luck on the animation problem?

@3210jr

This comment has been minimized.

3210jr commented Aug 17, 2017

Any solutions anyone?

@barrymichaeldoyle

This comment has been minimized.

@laclance

This comment has been minimized.

@spencercarli

This comment has been minimized.

Member

spencercarli commented Aug 28, 2017

Hi! In an effort to get the hundreds of issues on this repo under control I'm closing issues tagged as questions. Please don't take this personally - it's simply something we need to do to get the issues to a manageable point. If you still have a question I encourage you to re-read the docs, ask on StackOverflow, or ask on the #react-navigation Reactiflux channel. Thank you!

@rishabhbhatia

This comment has been minimized.

rishabhbhatia commented Sep 17, 2017

This is definitely an issue and still exist. Why is every pressing problem being marked as an question and being closed.

@yuanotes

This comment has been minimized.

yuanotes commented Oct 30, 2017

I really don't know how the reset action works. When I use it to RESET the stack to "log out", the navigator goes crazy.

I've no choice but hacked defaultGetStateForAction of the stack router to work around this:

const defaultGetStateForAction = AppNavigator.router.getStateForAction;
AppNavigator.router.getStateForAction = (action, currentState) => {
  // some other code such as blocking back action...
  if (action.type === 'user/logout') {
    return defaultGetStateForAction(NavigationActions.init());
  }
  return defaultGetStateForAction(action, currentState);
};

Hope this helps.

@nthgol

This comment has been minimized.

nthgol commented Apr 13, 2018

@yuanotes this worked! Add that to the collection of hacks I have to getStateForAction! Thanks

@xaphod

This comment has been minimized.

xaphod commented May 4, 2018

In case it helps someone: when rolling my own hamburger / drawer and needing to make sure you don't go "back" to it after using it to navigate, I had the issue that NavigationActions.reset was returning a state with routes that did NOT match what the actions I gave it.
My mistake was that I forgot to pass in the second parameter - the state. See the comment // *** HERE below.

      // if hamburger menu is in the routes stack for nextState, but NOT at the top
      // (ie it is behind something), then kill it so we don't go 'back' to it
      let pickerIndex = -1;
      const rebuildActions = [];
      nextState.routes.forEach((route, index) => {
        if (route.routeName.search(/Picker/i) === -1) {
          // not a picker - if we have to remove the picker then we have to manually rebuild the entire stack by actions, 
          // so start building those actions now
          rebuildActions.push(NavigationActions.navigate({ routeName: route.routeName }));
        } else {
          // it is a picker, save the index so we can check if the picker is actually on top
          pickerIndex = index;
        }
      });

      if (pickerIndex !== -1 && pickerIndex !== nextLastIndex) {
        const resetState = AppNavigator.router.getStateForAction(NavigationActions.reset({
          index: rebuildActions.length - 1,
          actions: rebuildActions,
        }), state); // *** HERE 

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