DEV Community

loading...
Cover image for Change stacks in react-navigation v5

Change stacks in react-navigation v5

bigtoni profile image Toni Vujević ・2 min read

The problem with nesting navigators in react-navigation v5 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 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 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.

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;
Enter fullscreen mode Exit fullscreen mode

This is example of Auth stack whose routes navigate to specific screens, you can add stacks like these as much as you want...

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;
Enter fullscreen mode Exit fullscreen mode

The solution is in 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.

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 }],
  });
}
Enter fullscreen mode Exit fullscreen mode

You need to add created ref from navigationService to NavigationContainer like this:

<NavigationContainer ref={navigationService.navigationRef}>
  <Navigation />
</NavigationContainer>
Enter fullscreen mode Exit fullscreen mode

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

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);
Enter fullscreen mode Exit fullscreen mode

Discussion (0)

pic
Editor guide