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/nativeand its required peer dependencies - Build a stack navigator with
HomeScreenandDetailsScreenconnected 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/drawerfor 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 navigatorreact-native-screens— uses native OS screen primitives for better memory performancereact-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 linkstep andMainActivity.javaedit 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.Screenmaps a routename(the string you pass tonavigation.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). NavigationContaineris 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-handlermust be properly configured on Android for gestures to work.
📚 Further Reading
- React Navigation official docs — the source of truth for setup, API, and advanced patterns
- Nesting navigators guide — how to compose stack, tab, and drawer navigators correctly
- react-native-screens — explains why native screen primitives improve performance and memory
- ⬅️ Previous: HTTP
- ➡️ Next: Libraries: Image Picker & React Elements