Introduction
React Navigation is the standard library for navigation in React Native. It provides stack, tab, drawer, and bottom tab navigators out of the box. For custom navigation flows, you can create a custom navigator using createNavigatorFactory and useNavigationBuilder, or customize built-in navigators with screen options, custom tab bars, and custom headers. The legacy Navigator component from early React Native is deprecated — use React Navigation v6+ instead.
Setting Up React Navigation
1# Install core dependencies
2npm install @react-navigation/native
3npm install react-native-screens react-native-safe-area-context
4
5# Install the navigator you need
6npm install @react-navigation/native-stack # Stack
7npm install @react-navigation/bottom-tabs # Bottom tabs
8npm install @react-navigation/drawer # Drawer
Basic Stack Navigation
1import React from 'react';
2import { NavigationContainer } from '@react-navigation/native';
3import { createNativeStackNavigator } from '@react-navigation/native-stack';
4import { View, Text, Button } from 'react-native';
5
6const Stack = createNativeStackNavigator();
7
8function HomeScreen({ navigation }) {
9 return (
10 <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
11 <Text>Home Screen</Text>
12 <Button
13 title="Go to Details"
14 onPress={() => navigation.navigate('Details', { itemId: 42 })}
15 />
16 </View>
17 );
18}
19
20function DetailsScreen({ route, navigation }) {
21 const { itemId } = route.params;
22 return (
23 <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
24 <Text>Details for item {itemId}</Text>
25 <Button title="Go Back" onPress={() => navigation.goBack()} />
26 </View>
27 );
28}
29
30export default function App() {
31 return (
32 <NavigationContainer>
33 <Stack.Navigator initialRouteName="Home">
34 <Stack.Screen name="Home" component={HomeScreen} />
35 <Stack.Screen name="Details" component={DetailsScreen} />
36 </Stack.Navigator>
37 </NavigationContainer>
38 );
39}
Custom Tab Bar
1import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
2import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
3
4const Tab = createBottomTabNavigator();
5
6function CustomTabBar({ state, descriptors, navigation }) {
7 return (
8 <View style={styles.tabBar}>
9 {state.routes.map((route, index) => {
10 const { options } = descriptors[route.key];
11 const label = options.tabBarLabel ?? options.title ?? route.name;
12 const isFocused = state.index === index;
13
14 const onPress = () => {
15 const event = navigation.emit({
16 type: 'tabPress',
17 target: route.key,
18 canPreventDefault: true,
19 });
20 if (!isFocused && !event.defaultPrevented) {
21 navigation.navigate(route.name);
22 }
23 };
24
25 return (
26 <TouchableOpacity
27 key={route.key}
28 onPress={onPress}
29 style={[styles.tab, isFocused && styles.activeTab]}
30 >
31 <Text style={[styles.tabText, isFocused && styles.activeText]}>
32 {label}
33 </Text>
34 </TouchableOpacity>
35 );
36 })}
37 </View>
38 );
39}
40
41const styles = StyleSheet.create({
42 tabBar: { flexDirection: 'row', height: 60, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#eee' },
43 tab: { flex: 1, justifyContent: 'center', alignItems: 'center' },
44 activeTab: { borderBottomWidth: 2, borderBottomColor: '#007AFF' },
45 tabText: { fontSize: 14, color: '#999' },
46 activeText: { color: '#007AFF', fontWeight: '600' },
47});
48
49export default function App() {
50 return (
51 <NavigationContainer>
52 <Tab.Navigator tabBar={(props) => <CustomTabBar {...props} />}>
53 <Tab.Screen name="Home" component={HomeScreen} />
54 <Tab.Screen name="Search" component={SearchScreen} />
55 <Tab.Screen name="Profile" component={ProfileScreen} />
56 </Tab.Navigator>
57 </NavigationContainer>
58 );
59}
1import { createNativeStackNavigator } from '@react-navigation/native-stack';
2import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
3
4const Stack = createNativeStackNavigator();
5
6function CustomHeader({ navigation, route, options }) {
7 return (
8 <View style={styles.header}>
9 {navigation.canGoBack() && (
10 <TouchableOpacity onPress={() => navigation.goBack()}>
11 <Text style={styles.backButton}>← Back</Text>
12 </TouchableOpacity>
13 )}
14 <Text style={styles.headerTitle}>{options.title ?? route.name}</Text>
15 <TouchableOpacity onPress={() => navigation.navigate('Settings')}>
16 <Text style={styles.headerAction}>⚙</Text>
17 </TouchableOpacity>
18 </View>
19 );
20}
21
22const styles = StyleSheet.create({
23 header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', height: 56, paddingHorizontal: 16, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: '#eee' },
24 headerTitle: { fontSize: 18, fontWeight: '600' },
25 backButton: { fontSize: 16, color: '#007AFF' },
26 headerAction: { fontSize: 20 },
27});
28
29export default function App() {
30 return (
31 <NavigationContainer>
32 <Stack.Navigator
33 screenOptions={{
34 header: (props) => <CustomHeader {...props} />,
35 }}
36 >
37 <Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Dashboard' }} />
38 <Stack.Screen name="Details" component={DetailsScreen} />
39 </Stack.Navigator>
40 </NavigationContainer>
41 );
42}
Nested Navigators
1import { createNativeStackNavigator } from '@react-navigation/native-stack';
2import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
3
4const HomeStack = createNativeStackNavigator();
5const Tab = createBottomTabNavigator();
6
7function HomeStackNavigator() {
8 return (
9 <HomeStack.Navigator>
10 <HomeStack.Screen name="Feed" component={FeedScreen} />
11 <HomeStack.Screen name="Post" component={PostScreen} />
12 </HomeStack.Navigator>
13 );
14}
15
16export default function App() {
17 return (
18 <NavigationContainer>
19 <Tab.Navigator>
20 <Tab.Screen
21 name="Home"
22 component={HomeStackNavigator}
23 options={{ headerShown: false }}
24 />
25 <Tab.Screen name="Profile" component={ProfileScreen} />
26 </Tab.Navigator>
27 </NavigationContainer>
28 );
29}
Conditional Navigation (Auth Flow)
1function App() {
2 const [isLoggedIn, setIsLoggedIn] = React.useState(false);
3
4 return (
5 <NavigationContainer>
6 {isLoggedIn ? (
7 <Tab.Navigator>
8 <Tab.Screen name="Home" component={HomeScreen} />
9 <Tab.Screen name="Profile" component={ProfileScreen} />
10 </Tab.Navigator>
11 ) : (
12 <Stack.Navigator>
13 <Stack.Screen name="Login" component={LoginScreen} />
14 <Stack.Screen name="Register" component={RegisterScreen} />
15 </Stack.Navigator>
16 )}
17 </NavigationContainer>
18 );
19}
Common Pitfalls
Using the deprecated Navigator component: The original Navigator from React Native core was removed years ago. Use @react-navigation/native (v6+) which is actively maintained and provides better performance, TypeScript support, and customization options.
Wrapping NavigationContainer multiple times: NavigationContainer must be rendered exactly once at the root of your app. Nesting it inside another NavigationContainer causes navigation state conflicts and crashes. Use nested navigators (Stack.Navigator inside Tab.Navigator) without extra containers.
Not passing navigation prop to custom components: Custom tab bars and headers receive navigation, state, and descriptors as props from the navigator. Forgetting to destructure and use these props causes navigation.navigate is not a function errors.
Hiding the header without headerShown: Setting headerMode: 'none' is deprecated. Use screenOptions={{ headerShown: false }} on the navigator or options={{ headerShown: false }} on individual screens to hide the header.
Deep linking without linking configuration: Custom navigation flows that support deep links (URLs opening specific screens) require a linking prop on NavigationContainer. Without it, deep links are ignored silently. Define a linking config with path mappings for each screen.
Summary
Use @react-navigation/native v6+ for all React Native navigation (the legacy Navigator is deprecated)
Customize tab bars with the tabBar prop on Tab.Navigator
Customize headers with the header screen option on Stack.Navigator
Nest navigators (stack inside tabs, tabs inside drawer) for complex navigation flows
Use conditional rendering inside NavigationContainer for auth flows
Install react-native-screens and react-native-safe-area-context for proper native behavior