React Native Navigation: Complete Guide

Navigation is the backbone of any mobile app. Users expect to move between screens, go back, switch tabs, and access side menus — all with smooth animations and predictable behaviour. React Navigation is the de facto standard library for handling all of this in React Native. This guide walks you through the three core navigator types with complete, working TypeScript code.

Installation and Setup

First, install React Navigation and its dependencies. The library is modular, so you install only what you need:

# Core library
npm install @react-navigation/native

# Required peer dependencies
npm install react-native-screens react-native-safe-area-context

# Navigator packages (install the ones you need)
npm install @react-navigation/native-stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer

# Drawer requires gesture handler and reanimated
npm install react-native-gesture-handler react-native-reanimated

# iOS pods
cd ios && pod install && cd ..

Wrap your app with NavigationContainer — this is the top-level component that manages the navigation tree:

ADVERTISEMENT
// App.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { RootNavigator } from './navigation/RootNavigator';

export default function App(): React.JSX.Element {
  return (
    <NavigationContainer>
      <RootNavigator />
    </NavigationContainer>
  );
}

Stack Navigator: Screen-to-Screen Navigation

Stack navigation mimics a deck of cards — each new screen slides on top of the previous one. Define your type-safe route parameters first, then create the navigator:

// navigation/types.ts
export type RootStackParamList = {
  Home: undefined;
  Details: { itemId: number; title: string };
  Profile: { userId: string };
};

// screens/HomeScreen.tsx
import React from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/types';

type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;

const ITEMS = Array.from({ length: 20 }, (_, i) => ({
  id: i + 1,
  title: `Item ${i + 1}`,
}));

export function HomeScreen({ navigation }: Props): React.JSX.Element {
  return (
    <FlatList
      data={ITEMS}
      keyExtractor={(item) => item.id.toString()}
      contentContainerStyle={styles.list}
      renderItem={({ item }) => (
        <TouchableOpacity
          style={styles.card}
          onPress={() =>
            navigation.navigate('Details', {
              itemId: item.id,
              title: item.title,
            })
          }
        >
          <Text style={styles.cardTitle}>{item.title}</Text>
          <Text style={styles.cardArrow}>&rarr;</Text>
        </TouchableOpacity>
      )}
    />
  );
}

const styles = StyleSheet.create({
  list: { padding: 16 },
  card: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    marginBottom: 8,
    backgroundColor: '#fff',
    borderRadius: 8,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  cardTitle: { fontSize: 16, fontWeight: '600' },
  cardArrow: { fontSize: 18, color: '#6366F1' },
});

// screens/DetailsScreen.tsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/types';

type Props = NativeStackScreenProps<RootStackParamList, 'Details'>;

export function DetailsScreen({ route, navigation }: Props): React.JSX.Element {
  const { itemId, title } = route.params;

  React.useEffect(() => {
    navigation.setOptions({ title });
  }, [navigation, title]);

  return (
    <View style={styles.container}>
      <Text style={styles.heading}>{title}</Text>
      <Text style={styles.detail}>Item ID: {itemId}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  heading: { fontSize: 28, fontWeight: '700', marginBottom: 8 },
  detail: { fontSize: 16, color: '#666' },
});

Now wire the stack navigator:

// navigation/StackNavigator.tsx
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';
import { HomeScreen } from '../screens/HomeScreen';
import { DetailsScreen } from '../screens/DetailsScreen';

const Stack = createNativeStackNavigator<RootStackParamList>();

export function StackNavigator(): React.JSX.Element {
  return (
    <Stack.Navigator
      initialRouteName="Home"
      screenOptions={{
        headerStyle: { backgroundColor: '#4F46E5' },
        headerTintColor: '#fff',
        headerTitleStyle: { fontWeight: '700' },
      }}
    >
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Details" component={DetailsScreen} />
    </Stack.Navigator>
  );
}

Tab Navigator: Bottom Navigation

Tabs let users switch between top-level sections. This is the most common navigation pattern in mobile apps:

// navigation/TabNavigator.tsx
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { StackNavigator } from './StackNavigator';
import { ProfileScreen } from '../screens/ProfileScreen';
import { SettingsScreen } from '../screens/SettingsScreen';

type TabParamList = {
  HomeTab: undefined;
  ProfileTab: undefined;
  SettingsTab: undefined;
};

const Tab = createBottomTabNavigator<TabParamList>();

export function TabNavigator(): React.JSX.Element {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        headerShown: false,
        tabBarActiveTintColor: '#4F46E5',
        tabBarInactiveTintColor: '#9CA3AF',
        tabBarIcon: ({ color, size }) => {
          const icons: Record<string, string> = {
            HomeTab: '\u2302',
            ProfileTab: '\u263A',
            SettingsTab: '\u2699',
          };
          return (
            <Text style={{ fontSize: size, color }}>
              {icons[route.name] || '?'}
            </Text>
          );
        },
      })}
    >
      <Tab.Screen
        name="HomeTab"
        component={StackNavigator}
        options={{ tabBarLabel: 'Home' }}
      />
      <Tab.Screen
        name="ProfileTab"
        component={ProfileScreen}
        options={{ tabBarLabel: 'Profile' }}
      />
      <Tab.Screen
        name="SettingsTab"
        component={SettingsScreen}
        options={{ tabBarLabel: 'Settings' }}
      />
    </Tab.Navigator>
  );
}

Notice that HomeTab nests the entire StackNavigator inside a tab. This is a standard pattern — each tab can have its own navigation stack, so pushing a details screen does not affect other tabs.

Drawer Navigator: Side Menu

Drawers provide a slide-out menu, typically accessed via a hamburger icon or swipe gesture:

// navigation/DrawerNavigator.tsx
import React from 'react';
import {
  createDrawerNavigator,
  DrawerContentScrollView,
  DrawerItemList,
  DrawerItem,
} from '@react-navigation/drawer';
import { View, Text, StyleSheet } from 'react-native';
import { TabNavigator } from './TabNavigator';
import { AboutScreen } from '../screens/AboutScreen';

type DrawerParamList = {
  Main: undefined;
  About: undefined;
};

const Drawer = createDrawerNavigator<DrawerParamList>();

function CustomDrawerContent(props: any): React.JSX.Element {
  return (
    <DrawerContentScrollView {...props}>
      <View style={styles.header}>
        <Text style={styles.headerText}>MyApp</Text>
        <Text style={styles.headerSub}>v1.0.0</Text>
      </View>
      <DrawerItemList {...props} />
      <DrawerItem
        label="Logout"
        onPress={() => console.log('Logout pressed')}
      />
    </DrawerContentScrollView>
  );
}

export function DrawerNavigator(): React.JSX.Element {
  return (
    <Drawer.Navigator
      drawerContent={(props) => <CustomDrawerContent {...props} />}
      screenOptions={{
        drawerActiveTintColor: '#4F46E5',
        headerStyle: { backgroundColor: '#4F46E5' },
        headerTintColor: '#fff',
      }}
    >
      <Drawer.Screen name="Main" component={TabNavigator} />
      <Drawer.Screen name="About" component={AboutScreen} />
    </Drawer.Navigator>
  );
}

const styles = StyleSheet.create({
  header: { padding: 20, borderBottomWidth: 1, borderBottomColor: '#E5E7EB' },
  headerText: { fontSize: 20, fontWeight: '700' },
  headerSub: { fontSize: 14, color: '#6B7280', marginTop: 4 },
});

Combining All Three: The Root Navigator

In a production app, you often nest navigators: a drawer wraps tabs, and each tab wraps a stack. The RootNavigator ties it all together:

// navigation/RootNavigator.tsx
import React from 'react';
import { DrawerNavigator } from './DrawerNavigator';

export function RootNavigator(): React.JSX.Element {
  // In a real app you might check auth state here
  // and conditionally render an auth stack
  return <DrawerNavigator />;
}

The nesting hierarchy is: Drawer → Tabs → Stack. Each level manages its own navigation state independently.

Conclusion

React Navigation gives you the building blocks to create any navigation structure your app needs. Start simple with a stack navigator, add bottom tabs as your app grows, and introduce a drawer when you have enough top-level sections to justify it. The TypeScript type definitions ensure you never pass the wrong parameters to a screen, catching bugs at compile time instead of runtime. For advanced patterns like deep linking, authentication flows, and shared element transitions, the official React Navigation documentation at reactnavigation.org is comprehensive and well-maintained.

ADVERTISEMENT

Leave a Comment

Your email address will not be published. Required fields are marked with an asterisk.