react-navigation / react-navigation

Routing and navigation for your React Native apps
https://reactnavigation.org
23.33k stars 4.98k forks source link

How to goBack from nested StackNavigator? #697

Closed Twinski closed 7 years ago

Twinski commented 7 years ago

Hey everyone! šŸ˜„ I've been trying a lot of things but nothing worked out so far.. šŸ¤”

const InsuranceAddStackNavigator = StackNavigator({
    Teaser: { screen: TeaserPage },
    Quote: { screen: QuotePage },
    TermsConditions: { screen: TermsConditionsPage },
    Pay: { screen: PayPage },
    Success: { screen: SuccessPage },
}

const RootNavigator = StackNavigator({
    Main: { 
      screen: MainNavigator,
    },
    InsuranceAdd: { 
      screen: InsuranceAddNavigator,
    },
}, {
  headerMode: 'none',
  mode: 'modal'
});

export default RootNavigator;

I can navigate to InsuranceAddNavigator without a problem. In InsuranceAddNavigator (which is used to go through 5 steps > Next > Next > Next ...) I'd like to be able to dismiss the whole InsuranceAddNavigator-flow from any screen inside it (eg. Teaser, Quote, Terms, ..).

I've tried to call this.props.navigation.dispatch({ type:'Navigate/BACK', routeName:null }); Cause my modalNavigator has no routeName, correct? How can I say to my ModalNavigator: quit the InsuranceAdd-flow?

joemckie commented 7 years ago

Have you tried using a routeName to go back to, i.e. goBack('Main')?

wenbinf commented 7 years ago

I had same problem. After doing quite a lot of research, I still couldn't find any clean & elegant solution that I could deeply understand.

So I ended up putting multiple "steps" into one single screen, so goBack() from any step of this single screen would exit the entire flow.

The implementation is actually not too bad, as I could 100% understand what's going on there and structure the code in a maintainable way :)

ericvicenti commented 7 years ago

The Navigate/BACK action can take a key, which can specify exactly which screen to go back from. Each screen can see its key by looking at this.props.navigation.state.key

Twinski commented 7 years ago

@ericvicenti
Thanks for the suggestion. I'll have a look at it.

InteractiveLogic commented 7 years ago

@Twinski Were you able to solve this? I have a similar situation -- a tab navigator inside a stack navigator and I need to go back/pop the top-level stack navigator from any of the screens associated with the tabs in the nested tab navigator, and none of the things I've tried have worked... so I'm just wondering how you were able to ultimately solve your problem.

Twinski commented 7 years ago

I didn't give a try to be honest. Right now I switched to wix's library for native navigation. Airbnb also released their navigation library.

I would try to solve some of your problems in combination with Redux? I wrote my own tab navigator to have a complete custom tabbar (with a big plus button in the middle) & to be able to select a tab from anywhere in the app.

ā€” Dispatch action "NAVIGATE_TO_TAB" with { tabbar: "home", tab: "profile" } as payload for example. Hope this gives you some inspiration to tackle your problem ;)

xiemeilong commented 7 years ago

I can't goback() even with a key

dhruvparmar372 commented 7 years ago

I'm facing a similar issue. this is my current setup

const AppNavigator = StackNavigator({
  Login: { screen: LoginScreen },
  Splash: { screen: SplashScreen },
  Timeline: { screen: TimelineNavigator },
});

const TimelineNavigator = StackNavigator({
  Timeline: { screen: TimelineScreen },
  Task: { screen: TaskNavigator },
});

const TaskNavigator = StackNavigator({
  Task: { screen: TaskScreen },
});

The route flow I have in application

Splash -> Reset to Timeline -> Navigate to Task -> Go back to Timeline

For the last step, I'm trying goBack() & goBack('Task') from TaskScreen but it does not go back to TimelineScreen. Am I missing something here?

Germinate commented 6 years ago

@dhruvparmar372 According to the NOTE in the doc

a navigator's navigation prop may not have the helper functions (navigate, goBack, etc); it may only have state and dispatch.

So, use dispatch instead of goBack when the goBack not working. Example: this.props.navigation.dispatch(NavigationActions.back())

See the example here to learn more.

anishu23 commented 6 years ago

@Germinate That did work. But may I ask why goBack() didn't work?

sigridbra commented 6 years ago

I'm having trouble with something similar. The set up is a StackNavigator nested within a TabNavigator within a StackNavigator. It workes perfectly except navigating to the outermost stacks initial screen.

I tried the this.props.navigation.dispatch(NavigationActions.back(<ScreenName>)) but I get an error like In this environment the sources for assign MUST be an object.This error is a performance optimization and not spec compliant. What is happening?

Edit: Tried to use the key suggested by @ericvicenti. let goBack = NavigationActions.back({ key: key });' 'this.props.navigation.dispatch(goBack); where the key is the key of the second screen of the outermost StackNavigator. This does nothing.

lorenzsell commented 6 years ago

@Germinate's solution worked for me.

mrlaunch commented 6 years ago

I have exactly same issue with @Twinski. Any solutions?

jimfilippou commented 6 years ago
import { NavigationActions } from 'react-navigation'

const backAction = NavigationActions.back({
  key: null
}) 

this.props.navigation.dispatch(backAction);

This should do the trick!

NamanAulakh commented 6 years ago

@sigridbra I also faced similar problem. I solved it as follows:

navigation.dispatch(
      NavigationActions.reset({
        index: 1,
        actions: [
          NavigationActions.navigate({ routeName: 'Router' }),
          NavigationActions.navigate({ routeName: 'ProfilePage' }),
        ],
      }),
    )

Refer docs

beausmith commented 6 years ago

@Twinski Found this Issue when looking for a solution. @Germinate's solution worked for going back one screen at a time, but I needed to reset back many screens. Ultimately this solution worked for my similar use case: https://github.com/react-community/react-navigation/issues/1127#issuecomment-295841343

awinograd commented 6 years ago

I was also a little confused by the API here. It seems like the most common use case of wanting to go back to something other than the immediately previous screen (which you can get with just NavigationActions.back() is to go back to a given screen in the navigation state rather than back from. Here's some untested, use at your own risk, code.

function getKeyToGoBackToRouteName(
  state: NavigationState,
  routeName: string
) {
  // reverse the routes so we go back to the most recent route with name=RouteName
  const reversedRoutes = state.routes.slice(0).reverse();

  const index = reversedRoutes.findIndex(r => {
    return (
      // Check if the route is the one we want
      r.routeName === routeName ||
      // Or if this is a nested Navigator route, recursively check to see if
      // its children match
      !!(r.routes && getKeyToGoBackToRouteName(r, routeName))
    );
  });

  // If we didn't find the route, then return immediately
  if (index === -1) return null;

  // We want the key of the route _after_ routeName so that when we go back from
  // key, we are at routeName
  // Since we have reversed the routes, later routes have smaller indices
  const route = reversedRoutes[index - 1];
  return route ? route.key : null;
}

NavigationActions.back(getKeyToGoBackToRouteName(navigationState, 'RouteToBeActiveAfterThisAction'));
Guihgo commented 6 years ago

I did all that everyone above say to do. After read (react navegation code), i understood and now I can do that !

In my case i have 3 router : A -> B -> C and i want to go from C to A directly. So i do this:

on constructor in B.js:

constructor(props) {
    super(props);
    this.props.navigation.state.key = 'BScreen'; //set the key's name that you want
}

on render in C.js:

render(){
    const { goBack } = this.props.navigation;
    return(
        <Button onPress={() => {goBack('BScreen')}}>Go BACK!</Button>
    );
}

FIX BUG ( creating screen B twice) (RECOMEND do this):

on open screen C in B.js: this.props.navigation.navigate('CScreen', {keyBScreen: this.props.navigation.state.key});

on back button in C.js:

render(){
    const { params } = this.props.navigation.state;
    const { goBack } = this.props.navigation;
    return(
        <Button onPress={() => {goBack(params.keyBScreen)}}>Go BACK!</Button>
    );
}
MatheusLima7 commented 6 years ago

@Germinate It did not work for me. Always returns to the initial route. I wanted to go back to the previous page.

Guihgo commented 6 years ago

@MatheusLima7 see my comment

bluesockets commented 6 years ago

@MatheusLima7 Your solution worked for me! Here's sample code:

const RootNavigator = StackNavigator({
    Main: { 
      screen: MainNavigator,
    },
    Something: { 
      screen: SomethingNavigator,
    },
});

const SomethingNavigator = StackNavigator({
    Step1: { screen: Step1 },
    Step2: { screen: Step2 },
    Step3: { screen: Step3 },
}

export default RootNavigator;

My code is set up to conditionally go to either Step2 or Step3 from Step1. The behavior I want is for the back gesture to navigate to Step1 from either Step3 or Step2. Step2 already goes back to Step1 because of the stack order.

By using your suggestion, and setting the navigation state key to Step2 in Step3's constructor, the navigation works as I wanted!!

export default class Step3 extends React.Component {
  constructor(props) {
    super(props);
    this.props.navigation.state.key = 'Step2';
  }
...
}

Now, swiping back from either Step2 or Step3 it always pops me back to Step1.

Thanks!

rxh143030 commented 6 years ago

@Guihgo Thanks man, your code was an easy fix to my problem! However, i'm just wondering why it works?

MatheusLima7 commented 6 years ago

@Guihgo I just wanted to return a method to a previous page in a simple way. There are routes with parameters that would cause me many problems. GoBack for me would work as a simple return to the previous page.

wachunei commented 6 years ago

Just in case anyone needs a solution, goBack(null) did the trick for me.

Screen.navigationOptions = ({ navigation }) => ({
  headerRight: (
    <TouchableOpacity
      onPress={() => navigation.goBack(null)}
    >
      <Text>Back</Text>
    </TouchableOpacity>
  ),
});
LadyBethJ commented 6 years ago

thanks to the response of @Guihgo I've found what I needed to solve the problem: You got the screens A, B, C and D; and you navigate from A->B->C->D. Now you're on D and wants to go back to A. reactnavigation's doc gives you a few ways to do this:

this.props.navigation.goBack('B')
this.props.navigation.dispatch(
    NavigationActions.back({key:'B'})
)

We're using the key = 'B', because only the screen B can go back to the screen A. But the code above doesn't work, why? Because the key of the screen B is not 'B'; the key is another alphanumeric string which we don't know. So, to solve this, what we have to do is to set the key = 'B' in the constructor of screen B, as @Guihgo did in his comment:

constructor(props) {
    super(props)
    this.props.navigation.state.key = 'B'
}

Then, you could navigate to screen A from screen D, or C or any other screen. This worked for me, and I hope this could help.

beausmith commented 6 years ago

Check out this thread for a DismissableStackNavigator: How to dismiss modal containing a StackNavigator

And a second comment noting an option to add the dismiss method to screenProps

MrZhang123 commented 6 years ago

@Guihgo I try the code to fix bug ( creating screen B twice) , but it's not work , my react-navigation version is v1.5.8, how to fix ? why set key will render twice ?

remisaado commented 6 years ago

@LadyBethJ @Guihgo

I am using:

this.props.navigation.goBack('B')

and

this.props.navigation.state.key = 'B' // Tried in constructor and componentDidMount(), same result

However, I keep getting this yellow box error:

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the B component

And also it does not run my componentWillUnmount() function.

Does anyone know why I am getting this? The goBack navigation works great but why does this happen?

martjoao commented 6 years ago

I just spent a whole afternoon with this issue and solved it pretty simply.

My case: RootNav |-----TabNavigator |-----------TabA |-----------TabB |-----------TabC |-----StackNavigator |-----------StackA |-----------StackB

From TabC I launched stackNavigator in modal mode. I then navigated from StackA to StackB and wanted to dismiss my modal and get back to TabC.

I ended up accomplishing this with:

this.props.navigation.dispatch(NavigationActions.popToTop()); this.props.navigation.dispatch(NavigationActions.back());

kevinscience commented 6 years ago

@wachunei this.props.navigation.goBack(null) will fire constructor and componentDidMount, and fire componentWillUnmount improperly

daveteu commented 6 years ago

@kevinscience can you elaborate? Because so far navigation.goBack(null) seems to be the only solution that shows the intended behavior.

deepaksisodiya commented 5 years ago
 const currentRoutes = screenProps.rootNavState.routes;
 const currentTrustedContactRoutes = currentRoutes.find(route =>
     route.routeName === 'Navigator',
 );
 const key = currentTrustedContactRoutes.routes.find(route => route.routeName === 'Screen').key;
 navigation.goBack(key); // moved one back from 'Screen'
ujjwalagrawal17 commented 5 years ago

Thanks @Germinate :)

osikes commented 5 years ago

goBack(null) is the winner.

alainib commented 5 years ago

navigation.goBack(null) make my application freeze completly on android after i migrate from V1 to V2

i use now navigation.goBack()

nikilarigela commented 5 years ago

this.props.navigation.dispatch(NavigationActions.back()). It Works for me

DevJulianSalas commented 5 years ago

Thanks, @wachunei :)

javiermoli commented 5 years ago

@Germinate You rock!!!

ananth10 commented 5 years ago

@LadyBethJ @Guihgo , Can you tell me how to pass params with goBack?. i need to pass some params from ScreenC to ScreenA

nikilarigela commented 5 years ago

@ananth10 const resetAction = NavigationActions.reset({ index: 0, actions: [ NavigationActions.navigate({ routeName: 'ScreenA', params: { foo: 'bar' } }), ], });

ananth10 commented 5 years ago

@nikilarigela I dont want o reset stack, all i need is go back to Screen A from Screen C. it is working now . i have followed @Guihgo solution. but i need to pass some params to Screen A. if i reset stack and do navigate Screen A then all data reloading from server again. so i dont want to happen this.

nikilarigela commented 5 years ago

@ananth10 ok got it. Thanks

jdnichollsc commented 5 years ago

navigation.goBack() is not working properly as you can see here: https://github.com/proyecto26/react-native-modular Steps:

ghost commented 5 years ago

same issue as @remisaado. Using @Guihgo 's example will result in a new render for each time you switch between screens. For example going A->B = B renders once A - B - A - B = B rendering twice A - B - A - B - A - B = B rendering thrice

and so on. Setting the navigation key therefore is not very effective. Spent the afternoon trying to circumvent the multiple renders and couldnt find anythin

mi-mazouz commented 4 years ago

no solutions worked for me... this is very frustrating

stack1: a-b-c stack2: d-e-f

navigate() from f to c and goBack() on c => a

IbrahimCanKALYA commented 4 years ago

@mi-mazouz did you find any solution for this bug ?

mi-mazouz commented 4 years ago

@Twinski I definitely had to put it in another stack or at the top of the main stack

xtealer commented 4 years ago

I had same problem. After doing quite a lot of research, I still couldn't find any clean & elegant solution that I could deeply understand.

So I ended up putting multiple "steps" into one single screen, so goBack() from any step of this single screen would exit the entire flow.

The implementation is actually not too bad, as I could 100% understand what's going on there and structure the code in a maintainable way :)

I'm new to react native but i hope this helps. I have found a way to navigate to a parent container from it's child using navigation.navigate() function. You have to replace navigation.goBack property received from parent component with navigation.goBack = () => navigation.navigate('YourNotNestedRoute');...

LeDanielH commented 3 years ago

Until recently, we had navigation.goBack(null), for navigating out of nested stackNavigator. Because navigation.goBack() just showed us blank screen. But we noticed, isFocused listener stopped working whenever we called navigation.goBack(null). Actually all listeners stopped working ('willBlur', 'didFocus',....) - but I'm not sure that is the cause.

So this is what I found out:

I hope this helps.

haileymag commented 3 years ago

Thank you @LeDanielH , navigation.pop() helped me escape a drawer navigation that would not let me return to the other navigators I had.