Topic 9 of 15 · React Native

Topic 9 : Navigation

Lesson TL;DRTopic 9: Navigation 📖 8 min read · 🎯 intermediate · 🧭 Prerequisites: basicspickerstatusbarasyncstorage, http Why this matters Up until now, you've been building one screen at a time — and that's fi...
8 min read·intermediate·react-native · navigation · react-navigation · stack-navigator

Topic 9: Navigation

📖 8 min read · 🎯 intermediate · 🧭 Prerequisites: basics-picker-status-bar-async-storage, http

Why this matters

Up until now, you've been building one screen at a time — and that's fine for learning. But every real app has multiple screens: a login page, a home feed, a profile, settings. Without navigation, your users are stuck on that one screen forever. React Navigation is the library that connects all your screens together, lets users move between them, and remembers where they came from so they can go back. Today we'll look at three patterns you'll use in almost every app you build — stack, bottom tabs, and drawer navigation.

What You'll Learn

  • Install and configure @react-navigation/native and its required peer dependencies
  • Build a stack navigator with HomeScreen and DetailsScreen connected by a header and back button
  • Implement bottom tab navigation across four screens using @react-navigation/bottom-tabs
  • Add a side drawer with @react-navigation/drawer for menu-driven navigation

The Analogy

Think of navigation patterns like the layout of a city's transport network. A stack navigator is the subway: you board at one station and push deeper underground, and hitting "back" pops you back up to where you came from — history preserved in a neat stack. Bottom tabs are like the city's main bus lines displayed on a fixed panel at the base of every stop — always visible, always one tap away. A drawer navigator is the fold-out city map tucked inside your jacket: hidden until you swipe it open, revealing every destination at once. React Navigation gives you all three systems, and you can combine them however the city's layout demands.

Chapter 1: Setting Up React Navigation

React Navigation is split into focused packages so you install only the navigators you need. Every project starts with the core package plus two native dependencies for screen management and safe-area handling.

Step 1: Install React Navigation and Dependencies

npm install @react-navigation/native
npm install @react-navigation/stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer
npm install react-native-screens react-native-safe-area-context
  • @react-navigation/native — the core container and shared utilities
  • @react-navigation/stack — card-style push/pop transitions with a header
  • @react-navigation/bottom-tabs — a persistent tab bar along the bottom edge
  • @react-navigation/drawer — a swipeable side-menu navigator
  • react-native-screens — uses native OS screen primitives for better memory performance
  • react-native-safe-area-context — respects notches and home indicators on modern devices

Step 2: Install and Configure React Native Gesture Handler

React Navigation relies on react-native-gesture-handler for swipe gestures on both stack and drawer navigators.

npm install react-native-gesture-handler

For React Native versions below 0.60, you must also link the library manually:

react-native link react-native-gesture-handler

On Android, modify MainActivity.java so the gesture handler wraps the root view:

android/app/src/main/java/com/yourappname/MainActivity.java

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

public class MainActivity extends ReactActivity {

    @Override
    protected String getMainComponentName() {
        return "yourappname";
    }

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

React Native 0.60+ handles linking automatically via auto-linking, so the react-native link step and MainActivity.java edit are only required for older projects.

flowchart TD
    A[npm install @react-navigation/native] --> B[NavigationContainer]
    B --> C{Choose Navigator}
    C --> D[@react-navigation/stack\nStack.Navigator]
    C --> E[@react-navigation/bottom-tabs\nTab.Navigator]
    C --> F[@react-navigation/drawer\nDrawer.Navigator]
    D & E & F --> G[Screen components rendered inside]

Chapter 2: Setting Up Stack Navigation

Stack navigation pushes new screens on top of each other like a deck of cards. A header with a back button appears automatically, and pressing it pops the current screen off the stack.

Step 1: Create Screens

HomeScreen.js

import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

const HomeScreen = ({ navigation }) => {
  return (
    <View style={styles.container}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
};

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

export default HomeScreen;

The navigation prop is injected automatically by the stack navigator. Calling navigation.navigate('Details') pushes the Details screen onto the stack.

DetailsScreen.js

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const DetailsScreen = () => {
  return (
    <View style={styles.container}>
      <Text>Details Screen</Text>
    </View>
  );
};

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

export default DetailsScreen;

Step 2: Configure Stack Navigator

Wrap the entire app in NavigationContainer — this is the root context that owns navigation state. Inside it, declare your Stack.Navigator and register each screen with Stack.Screen.

App.js

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from './HomeScreen';
import DetailsScreen from './DetailsScreen';

const Stack = createStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
  • initialRouteName="Home" — the screen shown when the app first launches
  • Each Stack.Screen maps a route name (the string you pass to navigation.navigate()) to a component

Chapter 3: Implementing Bottom Tab Navigation

Bottom tab navigation keeps a persistent row of tabs at the bottom of the screen. Users can jump between top-level sections without losing their place in any of them.

Step 1: Create Additional Screens

SettingsScreen.js

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const SettingsScreen = () => {
  return (
    <View style={styles.container}>
      <Text>Settings Screen</Text>
    </View>
  );
};

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

export default SettingsScreen;

ProfileScreen.js

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const ProfileScreen = () => {
  return (
    <View style={styles.container}>
      <Text>Profile Screen</Text>
    </View>
  );
};

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

export default ProfileScreen;

Step 2: Configure Bottom Tab Navigator

Replace createStackNavigator with createBottomTabNavigator from @react-navigation/bottom-tabs. The API is identical — Tab.Navigator and Tab.Screen — so swapping navigators is low-friction.

App.js

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import HomeScreen from './HomeScreen';
import DetailsScreen from './DetailsScreen';
import SettingsScreen from './SettingsScreen';
import ProfileScreen from './ProfileScreen';

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Details" component={DetailsScreen} />
        <Tab.Screen name="Settings" component={SettingsScreen} />
        <Tab.Screen name="Profile" component={ProfileScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

Four tabs — Home, Details, Settings, Profile — appear at the bottom of the screen. Each tab preserves its own navigation state independently.

Chapter 4: Implementing Drawer Navigation

Drawer navigation hides a slide-in menu off the left (or right) edge of the screen. The user opens it by swiping from the edge or tapping a hamburger icon in the header.

Step 1: Configure Drawer Navigator

Import createDrawerNavigator from @react-navigation/drawer. The navigator API follows the same Drawer.Navigator / Drawer.Screen pattern.

App.js

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import HomeScreen from './HomeScreen';
import DetailsScreen from './DetailsScreen';
import SettingsScreen from './SettingsScreen';
import ProfileScreen from './ProfileScreen';

const Drawer = createDrawerNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator initialRouteName="Home">
        <Drawer.Screen name="Home" component={HomeScreen} />
        <Drawer.Screen name="Details" component={DetailsScreen} />
        <Drawer.Screen name="Settings" component={SettingsScreen} />
        <Drawer.Screen name="Profile" component={ProfileScreen} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

Every registered screen appears as a labelled row in the drawer menu automatically. Swipe right from the left edge (or call navigation.openDrawer() programmatically) to reveal it.

Combining Navigators

In real apps you rarely pick just one pattern. A common architecture nests a Stack inside each Tab, so each tab has its own push/pop history:

flowchart TD
    NC[NavigationContainer]
    NC --> TN[Tab.Navigator]
    TN --> HS[Home Tab\nStack.Navigator]
    TN --> SS[Settings Tab\nStack.Navigator]
    HS --> H[HomeScreen]
    HS --> D[DetailsScreen]
    SS --> S[SettingsScreen]
    SS --> P[ProfileScreen]

The key rule: NavigationContainer appears exactly once, at the root. All navigator nesting happens inside it.

🧪 Try It Yourself

Task: Build a two-tab app where the Home tab contains a stack navigator with HomeScreen and DetailsScreen, and the Settings tab shows a standalone SettingsScreen.

Success criterion: Tapping the "Go to Details" button on the Home tab pushes DetailsScreen with a back button in the header. Switching to the Settings tab and back to Home tab preserves where you were in the stack (you should still be on DetailsScreen).

Starter — App.js:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from './HomeScreen';
import DetailsScreen from './DetailsScreen';
import SettingsScreen from './SettingsScreen';

const Tab = createBottomTabNavigator();
const Stack = createStackNavigator();

function HomeStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Details" component={DetailsScreen} />
    </Stack.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="HomeTab" component={HomeStack} options={{ headerShown: false }} />
        <Tab.Screen name="Settings" component={SettingsScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

🔍 Checkpoint Quiz

Q1. Why does NavigationContainer need to wrap the entire navigator tree, and what happens if you nest two of them?

Q2. Given this HomeScreen component:

const HomeScreen = ({ navigation }) => (
  <Button title="Go" onPress={() => navigation.navigate('Profile')} />
);

The app throws "The action 'NAVIGATE' with payload {"name":"Profile"} was not handled by any navigator." What is the most likely cause?

A) navigation prop is undefined
B) Profile is not registered as a Screen in the navigator
C) NavigationContainer is missing
D) You must use navigation.push instead of navigation.navigate

Q3. What is the functional difference between @react-navigation/stack and @react-navigation/bottom-tabs, and when would you combine them?

Q4. The following App.js uses a drawer navigator but the swipe-to-open gesture doesn't work on Android. What is the missing setup step?

// App.js — no MainActivity.java changes made
import { createDrawerNavigator } from '@react-navigation/drawer';

A) npm install react-native-reanimated is missing
B) react-native-gesture-handler is installed but MainActivity.java was not updated to use RNGestureHandlerEnabledRootView
C) The NavigationContainer must set theme={DefaultTheme}
D) Drawer navigation does not support gestures on Android

A1. NavigationContainer owns the entire navigation state tree and provides context to all child navigators. Nesting two NavigationContainer components causes a runtime error because the inner one cannot inherit or sync state with the outer one — React Navigation expects a single root owner.

A2. B) Profile is not registered as a Screen in any navigator that is an ancestor of HomeScreen. navigation.navigate looks up the route name in the navigator tree; if it doesn't exist there, the action is unhandled.

A3. Stack navigator manages a history of screens pushed and popped with transitions and a header back button — ideal for detail flows. Bottom tabs keep multiple top-level screens reachable at all times without losing their individual histories. Combining them (a Stack inside each Tab) gives each section its own drill-down capability while preserving the persistent tab bar.

A4. B) On React Native < 0.60 (or when auto-linking didn't run), react-native-gesture-handler requires MainActivity.java to be updated to use RNGestureHandlerEnabledRootView as the root view. Without this, gesture events are not forwarded to the handler and swipe-to-open does not fire.

🪞 Recap

  • React Navigation is installed as separate focused packages: @react-navigation/native (core), plus one package per navigator type (stack, bottom-tabs, drawer).
  • NavigationContainer is the single root wrapper — every navigator and screen lives inside it.
  • Stack navigation (createStackNavigator) pushes/pops screens with a header and back button; navigation.navigate('RouteName') triggers the transition.
  • Bottom tab navigation (createBottomTabNavigator) renders a persistent tab bar; each tab preserves its own navigation state.
  • Drawer navigation (createDrawerNavigator) adds a swipeable side menu; react-native-gesture-handler must be properly configured on Android for gestures to work.

📚 Further Reading

Like this topic? It’s one of 15 in React Native.

Block your seat for ₹2,500 and join the next cohort.