React-Native
Navigation
Navigator Component
Mobile Development
Custom Navigation

Custom navigation with Navigator component in React-Native

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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

bash
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

jsx
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

jsx
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}

Custom Header

jsx
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

jsx
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)

jsx
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

Course illustration
Course illustration

All Rights Reserved.