Change stacks in react-navigation v5
Tags: javascript, react, navigation, reactnative
Published: 2020-10-26 09:28:49

The problem with nesting navigators in [react-navigation v5](https://reactnavigation.org/docs/nesting-navigators) is that it can sometimes be hard to block users from navigating to unwanted screens. It basically behaves like navigating to basic screens instead of separate stacks of screens. You will see [solutions](https://reactnavigation.org/docs/preventing-going-back) like hiding back button or interception goBack action, that just doesn't make sense to me, because it must be added in every place...

... and when you use example like [this](https://reactnavigation.org/docs/auth-flow) for switching from authFlow to appFlow you will add errors when navigating to unmounted routes and also you need to check the state in you root navigator.

So here is the solution that will allow you to use clean folder structure and still block users from unwanted navigation.

Add **RootStack** like this that contains **Auth** and **App stacks**, and also **Splash** stack as initial route. Splash stack is used just to call **initApp()** that checks if token exists or is still valid on server. 
 
```javascript
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';

import AppStack from './AppStack';
import AuthStack from './AuthStack';
import SplashStack from './SplashStack';

import { screenOptions } from './options';

const Stack = createStackNavigator();

const RootStack = () => {
  return (
    <Stack.Navigator
      initialRouteName="Splash"
      screenOptions={screenOptions}
      headerMode='none'
      mode='modal'
    >
      <Stack.Screen
        name="Auth"
        component={AuthStack}
      />
      <Stack.Screen
        name="App"
        component={AppStack}
      />
      <Stack.Screen
        name="Splash"
        component={SplashStack}
      />
    </Stack.Navigator>
  );
}

export default RootStack;
```
This is example of **Auth** stack whose routes navigate to specific screens, you can add stacks like these as much as you want... 

```javascript
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';

import SignInRequest from '../screens/login/request';
import SignInSuccess from '../screens/login/success';

import { screenOptions } from './options';

const Stack = createStackNavigator();

const AuthStack = () => {
  return (
    <Stack.Navigator
      initialRouteName="Request"
      screenOptions={screenOptions}
    >
      <Stack.Screen
        name="Request"
        component={SignInRequest}
        options={{
          title: 'SignInRequest',
        }}
      />
      <Stack.Screen
        name="Success"
        component={SignInSuccess}
        options={{ title: 'SignInSuccess' }}
      />
    </Stack.Navigator>
  );
}

export default AuthStack;
```

The solution is in [**resetRoot()**](https://reactnavigation.org/docs/navigation-container/#resetroot) function provider by react-navigation. I added navigation **navigation.service.js** like this so i can call functions on navigation anywhere from the project and wraped **resetRoot()** in my own function to easier usage.

```javascript
import * as React from 'react';

export const navigationRef = React.createRef();

export const navigate = (routeName, params) => {
  navigationRef.current?.navigate(routeName, params);
}

export const changeStack = (stackName) => {
  resetRoot(stackName)
}

const resetRoot = (routeName) => {
  navigationRef.current?.resetRoot({
    index: 0,
    routes: [{ name: routeName }],
  });
}
``` 
You need to add created **ref** from **navigationService** to **NavigationContainer** like this:

```javascript
<NavigationContainer ref={navigationService.navigationRef}>
  <Navigation />
</NavigationContainer>
```

now in your components or sagas or anywhere you just need to call **changeStack(...)** instead of **navigate(...)** whenever you need to dump old stack:

```javascript
function* requestLogin(action) {
  try {
    const { email, password } = action.payload;
    const emailLowerCase = email.toLowerCase();
    const { token } = yield call(userApis.login, emailLowerCase, password);
    yield put(userActions.setToken(token));

    yield call(getUserData);
    const userData = yield select(state => state.app.user);

    if (userData) {
      yield put(parcelsActions.loadAllParcels());
      yield call(navigationService.changeStack, 'App');
    } else {
      yield call(navigationService.changeStack, 'Auth');
    }
  } catch (e) {
    captureException(`Error requesting login: ${e}`);
  }
}

export function* requestLoginWatcher() {
  yield takeEvery(userActions.requestLogin.toString(), requestLogin);
```