React Native course ยท Module 3: Navigation

Stack Navigation - Underwater Corridors

4 min read ยท
In this lesson8

Welcome to the corridors of Atlantis! Poseidon introduces you to the secrets of navigation - the system that allows you to move between screens of your application like through corridors of an underwater palace.

What is React Navigation?

React Navigation is the most popular navigation library for React Native. It allows you to create different types of navigation: stack, tab, drawer, and many more.

Installing React Navigation in Expo

# Install core
npx expo install @react-navigation/native

# Dependencies for Expo
npx expo install react-native-screens react-native-safe-area-context

# Stack Navigator
npx expo install @react-navigation/stack
npx expo install react-native-gesture-handler

# Or Native Stack (recommended, faster)
npx expo install @react-navigation/native-stack

Basic Configuration

// App.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

// Define types for navigation parameters
type RootStackParamList = {
  Home: undefined;
  Details: { itemId: number; title: string };
  Profile: { userId: string };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen
          name="Home"
          component={HomeScreen}
          options={{ title: 'Palace of Atlantis' }}
        />
        <Stack.Screen
          name="Details"
          component={DetailsScreen}
          options={{ title: 'Chamber of Details' }}
        />
        <Stack.Screen
          name="Profile"
          component={ProfileScreen}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Home Screen with Navigation

import { NativeStackScreenProps } from '@react-navigation/native-stack';

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

function HomeScreen({ navigation }: HomeScreenProps) {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome to Atlantis!</Text>

      {/* Navigation to a screen without parameters */}
      <Pressable
        style={styles.button}
        onPress={() => navigation.navigate('Details', {
          itemId: 42,
          title: 'Poseidon\'s Chamber'
        })}
      >
        <Text style={styles.buttonText}>Enter the chamber</Text>
      </Pressable>

      {/* Using push instead of navigate */}
      <Pressable
        style={styles.button}
        onPress={() => navigation.push('Details', {
          itemId: 43,
          title: 'Triton\'s Chamber'
        })}
      >
        <Text style={styles.buttonText}>Discover the next chamber</Text>
      </Pressable>
    </View>
  );
}

Difference Between navigate and push

// navigate - if you are already on this screen it does nothing; otherwise it pushes it onto the stack
// (since React Navigation 7 it no longer goes back to a screen lower in the stack, use popTo for that)
navigation.navigate('Details', { itemId: 1 });

// push - always adds a new screen to the stack (even if it's the same)
navigation.push('Details', { itemId: 2 });

// Example: image gallery where each image opens Details
// push allows having multiple Details screens on the stack

Receiving Parameters

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

function DetailsScreen({ route, navigation }: DetailsScreenProps) {
  // Parameters are typed thanks to RootStackParamList
  const { itemId, title } = route.params;

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{title}</Text>
      <Text>Chamber ID: {itemId}</Text>

      {/* Go back to the previous screen */}
      <Pressable onPress={() => navigation.goBack()}>
        <Text>Go back</Text>
      </Pressable>

      {/* Go back to the first screen on the stack */}
      <Pressable onPress={() => navigation.popToTop()}>
        <Text>Return to the palace</Text>
      </Pressable>
    </View>
  );
}

Header Options

<Stack.Screen
  name="Details"
  component={DetailsScreen}
  options={({ route }) => ({
    title: route.params.title,
    headerStyle: {
      backgroundColor: '#1a5276',
    },
    headerTintColor: '#fff',
    headerTitleStyle: {
      fontWeight: 'bold',
    },
    // Button in the header
    headerRight: () => (
      <Pressable onPress={() => alert('Info!')}>
        <Text style={{ color: '#fff', marginRight: 10 }}>โ„น</Text>
      </Pressable>
    ),
  })}
/>

Dynamic Options with navigation.setOptions

function DetailsScreen({ route, navigation }: DetailsScreenProps) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Dynamically change the title
    navigation.setOptions({
      title: `Chamber #${route.params.itemId}`,
      headerRight: () => (
        <Text style={{ color: '#fff' }}>Visits: {count}</Text>
      ),
    });
  }, [navigation, route.params.itemId, count]);

  return (
    <View>
      <Pressable onPress={() => setCount(c => c + 1)}>
        <Text>Increase counter</Text>
      </Pressable>
    </View>
  );
}

Stack Navigation is the foundation of navigation in React Native. It works like a stack of cards - each new screen is placed on top, and going back removes the top card.

Code for this lesson: App.tsx
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

function HomeScreen({ navigation }) {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>Palace of Atlantis</Text>
      {/* Navigate to the 'Details' screen and pass the itemId param */}
      <Button
        title="Enter the chamber"
        onPress={() => navigation.navigate('Details', { itemId: 42 })}
      />
    </View>
  );
}

function DetailsScreen({ route }) {
  const { itemId } = route.params;
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>Chamber #{itemId}</Text>
    </View>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      {/* TODO: add a <Stack.Navigator> with the prop initialRouteName="Home" */}
      {/* TODO: inside it add <Stack.Screen name="Home" component={HomeScreen} /> */}
      {/* TODO: add a second <Stack.Screen name="Details" component={DetailsScreen} /> */}

    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#1a5276',
    gap: 16,
  },
  title: {
    fontSize: 22,
    color: '#fff',
    fontWeight: '600',
  },
});

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What must wrap all navigation in React Navigation?

  2. 2. What is the difference between navigation.navigate() and navigation.push()?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Vertical ordering

    Arrange Stack Navigator elements from outermost to innermost:

  • Click in order

    Click the elements in the order of passing parameters to a screen:

  • Code editor

    Complete the Stack Navigator code.

Useful articles