Kitavi Joseph React Native Developer

Nested React Native navigators

(using React Navigation v4)

9 min read 2715

Mobile applications normally have multiple screens that the user can interact with to access different functionalities on the app. The biggest challenge when developing a mobile app is figuring out how to bundle the screens together and providing the easiest way for the user to navigate and interact with these screens.

Most mobile applications will have a combination of navigators nested together to provide the best possible experience for navigating through the screens which improves the user experience on the app itself.

Prerequisites

To follow along with this article, you should have:

  • A basic understanding of React Native
  • React Native set up on your development machine

Why React Navigation?

When developing using React Native there are several options for navigation libraries you can use to implement the desired navigation you want on the app. Some of them include React Navigation, react-native-router-flux, and react-native-navigation.

In this blog, we will use React Navigation to implement the different navigators. React Navigation is easy to use, with a big community to help with issues along the base, and very detailed documentation. On top of all this, it is the recommended navigation library in the React Native docs.

Setup

To begin, we will need to set up the app where we will implement the navigation in. If you don’t have React Native set up please visit the React Native getting started docs and once you have that set up pick up from here.

Set up a React Native app using react-native init:

  npx react-native init NestedNavigation

This should set up a React Native project under the folder NestedNavigation . Make sure that runs properly on your simulator or device for either platform (iOS or Android). You should have the React Native welcome screen displaying, like this:

welcome to react native screen

Let’s move around a few files to get the app ready for some coding. Let’s create a src folder where most of our code will reside. Then move the App.js file into the created src folder, updating the file structure to look something like this:

We made a custom demo for .
No really. Click here to check it out.

react native app structure

Update the import path for App.js on index.js to include the updated folder name and reload your app:

// index.js
import { AppRegistry } from 'react-native'
import App from './src/App'
import { name as appName } from './app.json'

AppRegistry.registerComponent(appName, () => App)

Now, when your app reloads you should be able to see the welcome screen that is shown previously in this section.

Adding navigation library

For the setup, let’s install React Navigation which we will use for the navigation. Please visit the React Navigation v4 getting started page for the full details on setup. Here is a short walkthrough of the setup.

Install React Navigation v4 and it’s dependencies as shown below:

yarn add react-navigation@4.2.2

yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

For iOS be sure to install the pods:

cd ios; pod install; cd ..

For Android, we will need to make a few changes on MainActivity.java. Add the following lines:

...
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;

public class MainActivity extends ReactActivity {
  ...
  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
      @Override
      protected ReactRootView createRootView() {
        return new RNGestureHandlerEnabledRootView(MainActivity.this);
      }
    };
  }
}

After the platform setup, we’ll finish up with importing the gesture handler to index.js:

  // index.js
    import 'react-native-gesture-handler';

Now, run the app to confirm that setup was successful. You should see the “Welcome to React” screen again.

Before we get on adding the navigators, let’s have an overview of the navigation we will be seeking to implement. We will begin with a switch navigator for switching between application screens and authentication screens. Then we’ll have a drawer navigator which will then contain the tab navigator and stack navigator.

Adding a switch navigator

We are going to begin by setting up the switch navigator. This navigator, as the name suggests, switches between different screens( e.g. switching between the loading screen to either login screen or home screen). This navigator doesn’t handle back action and displays only one screen at a time which makes it ideal for authentication flows. We will add three screens for our switch navigator:

  • AuthLoading screen for checking login status
  • Login screen to handle authentication
  • Home screen for default landing screen

Create a components folder in src directory and create the screens mentioned above (AuthLoading.js, Login.js, and Home.js). Let’s add some simple code for the screens we’ve created. Copy the code below to each file and be sure to update the class names, display text, and exports to match each file:

// AuthLoading.js / Login.js / Home.js
import React from 'react'
import { StyleSheet, View, Text } from 'react-native'

class AuthLoading extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>AuthLoading Screen</Text>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
})

export default AuthLoading

Finally, we can get our switch navigator up and running.

Let’s jump on to App.js delete everything on there and replace it with this, then I’ll explain what magic we are trying to pull here:

// App.js
import React from 'react'
import { SafeAreaView, StyleSheet } from 'react-native'
import { createAppContainer, createStackNavigator } from 'react-navigation'

import Login from './components/Login'
import AuthLoading from './components/AuthLoading'
import Home from './components/Home'

const switchNavigator = createSwitchNavigator(
  {
    AuthLoading,
    Login,
    Home,
  },
  {
    initialRouteName: 'AuthLoading',
  },
)

const AppNavigator = createAppContainer(switchNavigator)

class App extends React.Component {
  render() {
    return (
      <SafeAreaView style={styles.container}>
        <AppNavigator />
      </SafeAreaView>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
})

export default App

We start with the usual, imports from react, react-native, and react-navigation. As seen above we import createSwitchNavigator used to set up the switch navigator and createAppContainer which is used as the root navigator in React Navigation.

Then we finalize the imports by adding the screens we want to use in the navigator (as seen in the set up for switch navigator). In the navigator configurations for the switch navigator, we set AuthLoading as the initial route, since it’s the first screen we want to load before switching to either the login or home screen depending on authentication status.

When we reload the app right now, it should display the AuthLoading screen as seen below without any navigation to either the login or home screens:

Auth loading screen in react native app

With this working, let’s give the  AuthLoading screen the power to switch between the other two screens depending on the authentication status. Since this isn’t a blog about authentication in React Native, we are going to hack our way around this:

// AuthLoading.js
...
import { ActivityIndicator, StyleSheet, View, Text } from 'react-native'


class AuthLoading extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      loggedIn: false,
    }
  }

  checkLoggedInStatus = () => {
    if (this.state.loggedIn) {
      return this.props.navigation.navigate('Home')
    }

    return this.props.navigation.navigate('Login')
  }

  componentDidMount() {
    this.checkLoggedInStatus()
  }

...

        <Text>AuthLoading Screen</Text>
        <ActivityIndicator />
...

We are going to add a state to store our virtual logged in status, which can either be true or false depending on whether or not the user is logged in. Then we will create a function checkLoggedInStatus that checks the user status and either navigates us to the home screen or login screen as needed. Then in our componentDidMount we will call a function so that it’s run every time AuthLoading is mounted. As a bonus, we also added an ActivityIndicator to show loading status before switching the app.

Feel free to play around with this by toggling the logged-in status and checking which screen is rendered.

Finally, let’s add a login button in Login screen to mimic a login and navigate the user to the home screen:

// Login.js
...
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native'

class Login extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Login Screen.</Text>
        <TouchableOpacity
          style={styles.loginButton}
          onPress={() => this.props.navigation.navigate('Home')}>
          <Text>Login</Text>
        </TouchableOpacity>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  loginButton: {
    height: 30,
    width: '40%',
    margin: 5,
    borderWidth: 1,
    borderRadius: 5,
    alignItems: 'center',
    justifyContent: 'center',
  },

...

We create a login screen that will be displayed when the user is not logged in and added a login button that mimics the user login and navigates the user to the home screen after successful login. With all this done, we should have a smooth switch navigator working and producing some navigation as seen below, checking the logged-in status and navigating to the login screen then home screen after logging in:

auth to home

Adding a drawer navigator

Drawer navigators provide users with very easy and intuitive navigation. Pulling the side menu either from the left or right to view more options and navigate between screens. When using the drawer navigator it has to be the base navigator for the rest of the app, hence why we are covering it first.

Let’s get to it. Let’s install the necessary requirements for the navigator:

yarn add react-navigation-drawer

Now let’s jump onto App.js and spin up the drawer navigator, this will require a few updates on this file to import the drawer navigator and set it up:

// App.js
...
import { createDrawerNavigator } from 'react-navigation-drawer' // import drawer nav

const DrawerNavigator = createDrawerNavigator(
  {
    Home, // move Home screen to drawer
  },
)

const switchNavigator = createSwitchNavigator(
  {
    AuthLoading,
    Login,
    DrawerNavigator, // change this to DrawerNavigator
  },
  {
    initialRouteName: 'AuthLoading',
  },
)

...

We import the createDrawerNavigator function and set up our DrawerNavigator with the home screen in it. We will add more screens to that later, of course, but for the initial setup let’s just go with the one for now and get the navigator up.

With the home screen moved into the drawer navigator, we replace it on the switch navigator with the newly created DrawerNavigator so that after the authentication flow the app navigates to the drawer navigator. Remember to update checkLoggedInStatus function on AuthLoading.js to navigate to the updated route name DrawerNavigator :

// AuthLoading.js
...

  checkLoggedInStatus = () => {
    if (this.state.loggedIn) {
      return this.props.navigation.navigate('DrawerNavigator')
    }
    return this.props.navigation.navigate('Login')
  }

With this setup, reload the app and it should navigate to the drawer navigator and open the home screen with the side menu ready to go. Swiping from the left should open it up and display the side menu with only the home screen option though.

navigate to drawer navigator

So let’s add another screen to be able to demonstrate navigation between different screens. In the components folder, create Settings.js file and copy the contents of Login.js into it, update the class names and make the followings updates to it:

// Settings.js
...

class Settings extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Settings Screen</Text>

        <TouchableOpacity
          style={styles.loginButton}
          onPress={() => this.props.navigation.openDrawer()}>
          <Text>Open Drawer</Text>
        </TouchableOpacity>
      </View>
    )
  }
}

...
export default Settings

As you noticed we addedopenDrawer() function that will open the drawer without the need to swipe from the left. Now to have view settings screen let’s add it to the DrawerNavigator in App.js:

// App.js
...
import Settings from './components/Settings'

const DrawerNavigator = createDrawerNavigator(
  {
    Home,
    Settings,
  },
  {
    initialRouteName: 'Home'
  }
)

...

We also set the initialRouteName in the drawer navigator configs so that the drawer navigator displays the home screen first. With this done our drawer navigation is good to go. Let’s reload our app and see where we are at.

You can further customize the side menu to your liking, but we won’t cover that in this post.

navigator opens to home screen

Add a tab navigator in drawer

Tab navigator uses a tab bar located on either the top or bottom of the screen that lets you switch between different routes. Screens on tab navigators don’t get mounted until they are focused. This navigator provides nice navigation for switching between content on the screen (for example, in WhatsApp when you switch between chats, status, and calls or on Twitter when you switch between home, search, notifications, and messages).

In React Navigation, there are a few options to set up your tab navigator, you can use either createBottomTabNavigator (default), createMaterialBottomTabNavigator, or createMaterialTopTabNavigator. As the name suggests the last two are material-design themed tabs located at the bottom or top respectively. In this walkthrough, we will use the default option (createBottomTabNavigator).

We will, of course, start by installing the required dependencies:

yarn add react-navigation-tabs

For simplicity, we will create one file WeekDays.js in our components folder which will contain three-tab components for Monday, Tuesday, and Wednesday which we will use in our App.js to set up the tab navigator:

// WeekDays.js
import React from 'react'
import { StyleSheet, View, Text } from 'react-native'

export class Monday extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Monday Blues</Text>
      </View>
    )
  }
}
export class Tuesday extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Tuesday Thoughts</Text>
      </View>
    )
  }
}
export class Wednesday extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Wednesday Wisdom</Text>
      </View>
    )
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
})

With the tab screens ready to go, it’s time to create the tab navigator, let’s jump into the App.js screen and get that going:

// App.js
...
import { createBottomTabNavigator } from 'react-navigation-tabs'
import { Monday, Tuesday, Wednesday } from './components/TabScreens'

const WeekDays = createBottomTabNavigator(
  {
    Monday,
    Tuesday,
    Wednesday,
  }
)

const DrawerNavigator = createDrawerNavigator(
  {
    Home,
    Settings,
    WeekDays, // add bottom navigator here
  },
  ...
)

...

With this in place, we should have WeekDays option on the side menu, which will then display the different tab options as seen below:

side menu with week days listed

 

Adding a stack navigator

Finally the last navigator we will talk about is the stack navigator. This navigator moves between different screens by placing the new screens on top of a stack. Stack navigator is pretty simple — a new screen is placed on top of the stack and to view previous screens you just navigate back.

yarn add react-navigation-stack @react-native-community/masked-view

For this, we are going to add profile settings to our app, which ideally should be on the same stack with settings. Let’s create ProfileSettings.js and then set up the settings stack navigation:

// ProfileSettings.js
import React from 'react'
import { StyleSheet, View, Text } from 'react-native'

class ProfileSettings extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Profile Settings Screen</Text>
      </View>
    )
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
})
export default ProfileSettings

With this ready now we can make final changes to our App.js to add our stack navigator. After necessary imports, we initialize the stack navigator and move settings screen into it as well as the new ProfileSettings. On the drawer navigator, we also update it to point to the AllSettings stack navigator that contains our settings screen:

// App.js

...
import { createStackNavigator } from 'react-navigation-stack'
import ProfileSettings from './components/ProfileSettings'

const AllSettings = createStackNavigator(
  {
    Settings,
    ProfileSettings,
  }
)
const DrawerNavigator = createDrawerNavigator(
  {
    Home,
    AllSettings, // update this
    WeekDays,
  },
  {
    initialRouteName: 'Home',
  },
)

With this done, the app should run successfully, you should also be able to navigate to AllSettings from the drawer navigator which will display the settings screen. Also, notice there isn’t a way to navigate between the settings and profile settings screen. Let’s fix that from the settings screen by adding a button to navigate to profile settings from setting:

// Settings.js

...
        <TouchableOpacity
          style={styles.loginButton}
          onPress={() => this.props.navigation.openDrawer()}>
          <Text>Open Drawer</Text>
        </TouchableOpacity>

        <TouchableOpacity // add this
          style={styles.loginButton}
          onPress={() => this.props.navigation.navigate('ProfileSettings')}>
          <Text>Profile Settings</Text>
        </TouchableOpacity>

...

With this we should be able to navigate to profile settings using the button added above and navigate back using the native back button provided by the stack navigator, like this:
navigate back button in use

Conclusion

Voila, we are done with nested navigation!

We started out with a switch navigator to handle authentication states, then we created a drawer navigator which should be the base navigator when used and on top of that added the tab and stack navigators. The last two can be nested in whichever order you prefer.

The code for this can be found on Github.

all navigators being shown in the react native app

nbsp;

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Full visibility into your web apps

    LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

    In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

    .
    Kitavi Joseph React Native Developer

    Leave a Reply