How To Keep Track of the Current Route in React Navigation V5 Outside of a Screen

React Navigation is a great library for including routing and navigation in your React Native application. As of React Navigation 5.x, there are useful hooks that allow you to access the navigation state of your library. These hooks are great for when you don’t want to pass navigation information deep into your component tree.

The Issue

One of my recent projects had this requirement: show a modal when the application state meets some criteria, but only on certain screens. I initially wrote something like this:

function ModalComponent() {
  const route = useRoute();
  // custom hook that contains app state
  const appState = useAppState();
  const visible =
    shouldShowModal(appState) && route.name !== 'ScreenWithoutModal';
  return (
    <Modal visible={visible}>
      <Text>This is a modal</Text>
    </Modal>
  );
}

function HomeScreen() {
  return (
    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
      <Text>Home Screen</Text>
    </View>
  );
}

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <ModalComponent />
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Upon running this code, I got the following error:

Couldn’t find a route object. Is your component inside a screen in a navigator?

This revealed on drawback of these hooks – they are not usable outside of the navigation tree.

The Solution

So how can we get around this issue?

After doing some reading of the React Navigation documentation, I noticed a prop on the NavigationContaineronStateChange. This prop allows you to run a callback any time navigation state changes, including the current route.

function ModalComponent() {
  // custom hook that contains app state
  const appState = useAppState();
  const visible =
    shouldShowModal(appState) && appState.currentRouteName !== 'ScreenWithoutModal';
  return (
    <Modal visible={visible}>
      <Text>This is a modal</Text>
    </Modal>
  );
}

function HomeScreen() {
  return (
    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
      <Text>Home Screen</Text>
    </View>
  );
}

// Recursive function that extracts current route from navigation state
export const getCurrentRoute = (
  state: NavigationState | Required<NavigationState['routes'][0]>['state'],
): Routes | undefined => {
  if (state.index === undefined || state.index < 0) {
    return undefined;
  }
  const nestedState = state.routes[state.index].state;
  if (nestedState !== undefined) {
    return getCurrentRoute(nestedState);
  }
  return state.routes[state.index].name;
};

const Stack = createNativeStackNavigator();

function App() {
  const appState = useAppState();
  const onNavigationStateChanged = (state: NavigationState | undefined) => {
    if (state === undefined) {
      appState.dispatch(onCurrentRouteChanged(undefined));
    } else {
      appState.dispatch(onCurrentRouteChanged(getCurrentRoute(state)));
    }
  };
  return (
    <NavigationContainer onStateChange={onNavigationStateChanged}>
      <ModalComponent />
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

This solution allows us to track our current route in our main app state context!

React Navigation V5 Outside of a Screen

The hooks provided by React Navigation are still useful for tracking navigation state. However, if you find yourself in a situation where you need to access to navigation state outside of a screen, give my solution a try!