26

I have a back button that takes the user a screen back, but when there are no screens left to go back, I want it to do something else so this is my code:

<Button onPress={()=>{
   if(CanGoBack){ // imaginary 'CanGoBack' variable
     this.props.navigation.goBack()
   }else{
     this.doSomething()
   }
}}/>

how can I achieve this?

Reza Shoja
  • 887
  • 2
  • 11
  • 24
  • How about disabling the back button if there are no screen left to go back? – herodrigues Feb 14 '19 at 23:13
  • 1
    I said that thinking about UX. If there's no more screen to go back, then you've reached some kind of initial screen. So the CanGoBack condition should be a condition to check if the current screen is not the initial screen. – herodrigues Feb 14 '19 at 23:18
  • @herodrigues My initial screen is not recognizable, there are four screens in tab navigator, so my app starts with the initial screen but after some navigation the initial screen might be derived from another screen! So in I won't know if goBack() can navigate back a screen or not – Reza Shoja Feb 14 '19 at 23:38
  • What kind of navigator you are using? `tabNavigator`, `stackNavigator`? – OriHero Feb 15 '19 at 09:19
  • @AbdumutalAbdusamatov hi, `tabNavigator` – Reza Shoja Feb 16 '19 at 09:14
  • React Navigation 5 introduced `navigation.canGoBack()` as mentioned by @salman-santino's answer. Check that people and save time as accepted answer is a bit outdated – rahul Jul 01 '20 at 21:48

5 Answers5

33

Note this answer was originally written for react-navigation v3.3.0. You should check the current documentation for react-navigation to make sure that this is still supported and if there are any changes/easier methods.

React-Navigation v3

There is a function in this.props.navigation called dangerouslyGetParent.

It states the following in the documentation:

Another good use case for this is to find the index of the active route in the parent's route list. So in the case of a stack if you are at index 0 then you may not want to render a back button, but if you're somewhere else in the list then you would render a back button.

So we can using the following to get the index of the route

this.props.navigation.dangerouslyGetParent().state.index

So we could use this in the onPress of your Button in the following way to check if we are back at the start of the route.

<Button onPress={() => {
  // get the index of the route
  let index = this.props.navigation.dangerouslyGetParent().state.index;
  // handle the index we get
  if (index > 0) {
    this.props.navigation.goBack();
  } else {
    this.doSomething();
  }
}}/>

Update for React-Navigation v5

From the documentation, we can see that dangerouslyGetParent still exists.

This method returns the navigation prop from the parent navigator that the current navigator is nested in. For example, if you have a stack navigator and a tab navigator nested inside the stack, then you can use dangerouslyGetParent inside a screen of the tab navigator to get the navigation prop passed from the stack navigator.

So it could be possible to use the above method for v3 in v5.


canGoBack() in v5

There is also an undocumented api called canGoBack(). This can be accessed from the navigation props in the following way:

this.props.navigation.canGoBack()

This property returns a boolean value if you are able to go back in the stack. Meaning we can update the code that we did for v3 in the following way.

<Button onPress={() => {
  // check to see if it is possible to go back
  let canGoBack = this.props.navigation.canGoBack();
  // handle what we do it we can/cannot go back
  if (canGoBack) {
    this.props.navigation.goBack();
  } else {
    this.doSomething();
  }
}}/>

However, as this is an undocumented api it is liable to change so it could be risky relying on it.


canGoBack() in v6

It now looks like that canGoBack has been documented you can find the information here

This method returns a boolean indicating whether there's any navigation history available in the current navigator, or in any parent navigators. You can use this to check if you can call navigation.goBack():

if (navigation.canGoBack()) {
  navigation.goBack();
}

Don't use this method for rendering content as this will not trigger a re-render. This is only intended for use inside callbacks, event listeners etc.

Andrew
  • 26,706
  • 9
  • 85
  • 101
  • 3
    this is solution is flawed, for example: it doesn't work with a tab navigation as root, containing stack navigations in each tab – Omar Awamry Dec 04 '20 at 19:26
  • You're welcome to write your own answer that covers the case that you describe. That way it helps other people who may have that specific problem. – Andrew Dec 04 '20 at 20:29
  • 2
    canGoBack may return an invalid value when pushing a screen. I was using it to render a back button (or not) and the button would appear on the first screen during the animation pushing the second screen onto the navigator. I added an answer below that solved my problem. – Kendrick Taylor Dec 05 '20 at 08:28
  • it still has some problems not always work – Sharif Al-Hayek Jan 31 '21 at 13:36
  • what is the solution in react-router-dom v6? @Andrew – Emma Teylor Jul 01 '22 at 17:32
5

On React navigation 5, there is a canGoBack() function which is super helpful in this case.

Salman
  • 588
  • 4
  • 10
  • How do I do that with hooks? – anonym Oct 04 '20 at 12:09
  • 1
    It's easier with hooks, as you can use `useNavigation` hook. ``` const navigation = useNavigation(); ``` and then : ``` if (navigation.canGoBack()) { navigation.goBack(); } ``` – Salman Oct 04 '20 at 17:27
  • Thank you. How would I do that in `ReactJS` with `react-router`? It doesn't come with useNavigation hook. – anonym Oct 05 '20 at 09:22
  • I don't think the concept of `canGoBack()` work in `Reactjs`. In a browser, user should go back and forth in and out of the site, right? If you want to restrict users, there are hooks `useLocation` and `useHistory` and then you can add custom logic with these. – Salman Oct 06 '20 at 17:48
  • it still has some problems not always work – Sharif Al-Hayek Jan 31 '21 at 13:36
  • what is the solution in react-router-dom v6? @Salman – Emma Teylor Jul 01 '22 at 17:26
  • 1
    @EmmaTeylor I'm guessing you are referring to the `react-router` library and `react-router-dom` for react. This question thread was originally about the `react native` navigation library (I know, naming is confusing here). As my previous comment, I think you need to handle it manually for the browser from react, as browser stores the history stack and can move back and forth. `useLocation` and `useHistory` can be useful here for custom logic for restricting user based on current url (ex: onboarding, payment). – Salman Jul 06 '22 at 05:04
3

There is a canGoBack() event. I am using React Navigation 5. Though I can't find it in the documentations. to check do console.log(this.props.navigation).

<Button onPress={()=>{
   if(this.props.navigation.canGoBack()){
     this.props.navigation.goBack()
   }else{
     this.doSomething()
   }
}}/>
Kash
  • 1,663
  • 3
  • 24
  • 33
2

This function returns a consistent (correct) value in v5 of React Navigation.

function can_go_back(route, navigation){
   const state = navigation.dangerouslyGetState();
   if(!state || !state.routes){ return(false); }
   const first_route = state.routes[0];
   if(!first_route){ return(false); }
   if(first_route.key === route.key){ return(false); }
   else{ return(true); }
};

or with hooks:

  const { dangerouslyGetState } = useNavigation()
  const route = useRoute()
  const canGoBack = dangerouslyGetState()?.routes[0]?.key !== route.key

You would use it with the headerLeft property, passing it the route, and navigation. Like this (modified example from the docs):

<Stack.Screen
  name="Home"
  component={HomeScreen}
  options={({ route, navigation }) => ({
    title: 'Awesome app',
    headerLeft: function(){
      if(!can_go_back(route, navigation){ return(null); }
      else{ 
         // return your button
      }
    },
  })}
/>

I had trouble with canGoBack. It returns an invalid value when pushing a screen. I was using it to render a back button (or not) and the button would appear on the first screen during the animation pushing the second screen onto the navigator.

plus-
  • 45,453
  • 15
  • 60
  • 73
Kendrick Taylor
  • 2,218
  • 2
  • 18
  • 21
1

If you're in need of using hooks, there are two hooks at your disposal; useNavigation and useNavigationState

Per the documentation of useNavigationState

useNavigationState is a hook which gives access to the navigation state of the navigator which contains the screen. It's useful in rare cases where you want to render something based on the navigation state.

import { useNavigationState } from '@react-navigation/native';

const Component = (props): React.FC<{}> => {
  //By calling useNavigationState
  const routesLength = useNavigationState(state => state.routes.length);
  //routesLength now holds the size of the routes.
  
  ...
  //Simply checking if routesLength > 0, then navigation can be done
  if(routesLength > 0) {
    props.navigation.navigate('ScreenToNavigate');
  }
  ...
};

If your component doesn't have access to the navigation or isn't part of the navigation hierarchy, the use of useNavigation is possible.

import { useNavigation} from '@react-navigation/native';

const Component = (): React.FC<{}> => {
  //By calling useNavigation
  const navigation = useNavigation();
  
  ...
  //E.g., in a button event listener
  if (navigation.canGoBack()) { 
    navigation.goBack();
  }
  ...
};

The canGoBack() method is undocumented but its explanation exists in the declaration file of @react-navigation/native

Check if dispatching back action will be handled by navigation. Note that this method doesn't re-render screen when the result changes. So don't use it in render.

knoxgon
  • 1,070
  • 2
  • 15
  • 31