Skip to content

Navigation Re-imagined: Mastering Expo Router - Advanced Routing

Posted on:May 6, 2025

Welcome to the Part 2 of Navigation Re-imagined: Mastering Expo Router, Developer đź‘‹

If you missed it, in Part 1 we covered:

Navigation can feel like solving a maze, but with Expo Router it becomes more like following clear signposts. In this part, we’ll add stacks, modals, and route groups, and explain every piece so that even non-technical readers can follow along.

Let’s continue our journey to make navigation simple and understandable!


Table of Contents

Open Table of Contents

Setting things up

For a quick start, make sure you have the Student Mate GitHub repository cloned and ready. You need to check out the branch post/navigation-expo-router-part-1. We’ll be building directly on top of it.

If you haven’t yet installed required packages:

cd student-mate
npm install

Stack Navigation Inside Tabs

What is Expo Router’s Stack feature?

A Stack is like a deck of cards: you place new screens on top and pop them off to go back. This pattern is great for drilling into details while keeping your tab context intact.

Think of your app as a book, developer. Tabs are the main chapters (like “Classes”), and stacks are the pages inside those chapters. You open a chapter (tab) and flip pages (stack screens) to see more details.

Does that make sense? Allow me to show you.

Folder Structure

app/
└── (tabs)/
    └── classes/
        ├── _layout.tsx   # Defines how the pages inside "Classes" work together
        ├── index.tsx     # The main Classes list
        └── [classId].tsx # A detail page for a specific class

Creating the Layout File

  1. Create the file app/(tabs)/classes/_layout.tsx.
  2. Move the existing app/(tabs)/classes.tsx file to app/(tabs)/classes and rename it to index.tsx.
  3. Import the Stack component.
  4. Define your screens inside .
// app/(tabs)/classes/_layout.tsx
import { Stack } from "expo-router";
 
/**
 * ClassesLayout sets up a Stack navigator:
 * - index.tsx shows the list of classes.
 * - [classId].tsx shows details for one class.
 */
export default function ClassesLayout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: { backgroundColor: "#f0f0f0" },
        headerTintColor: "#333",
      }}
    >
      {/* Main list of classes */}
      <Stack.Screen name="index" options={{ title: "Your Classes" }} />
      {/* Detail page for a selected class */}
      <Stack.Screen name="[classId]" />
    </Stack>
  );
}

How it works

  1. <Stack>: Think of this as our stack of pages.
  2. screenOptions: Sets a default look (header background & text color).
  3. <Stack.Screen name="index" />: Connects to index.tsx.
  4. <Stack.Screen name="[classId]" />: Connects to [classId].tsx, using the URL parameter to show which class.

What is a Modal in Expo Router?

Imagine a pop‑up window that floats above your book. That’s a modal — great for quick tasks like adding a new assignment without tearing the reader away from the main content.

Expo Router adds modal support via a special route group: any folder named with parentheses and containing a .tsx file will open as a modal overlay.

Create the Modals Folder

app/
└── (modals)/
    └── add-assignment.tsx  # The Add Assignment pop‑up
// app/(modals)/add-assignment.tsx
import { View, Text, TextInput, Button, StyleSheet } from "react-native";
import { router } from "expo-router";
 
/**
 * AddAssignmentModal:
 * - Overlay screen for adding a new assignment.
 * - Part of the navigation flow (not just a UI popup).
 */
export default function AddAssignmentModal() {
  return (
    <View style={styles.modal}>
      <Text style={styles.title}>âž• Add New Assignment</Text>
      <TextInput placeholder="Title" style={styles.input} />
      <TextInput placeholder="Due Date" style={styles.input} />
      <Button
        title="Save & Close"
        onPress={() => router.back()} // Closes the modal
      />
    </View>
  );
}
 
const styles = StyleSheet.create({
  modal: {
    flex: 1,
    justifyContent: "center",
    backgroundColor: "rgba(0,0,0,0.5)", // Semi-transparent backdrop
    padding: 20,
  },
  title: { fontSize: 24, color: "#fff", marginBottom: 20 },
  input: {
    backgroundColor: "#fff",
    padding: 10,
    marginBottom: 15,
    borderRadius: 6,
  },
});

Let’s break it down

How to Open the Modal

In app/(tabs)/assignments.tsx, add:

// app/(tabs)/assignments.tsx
import { router } from "expo-router";
import { Button, Pressable, StyleSheet, Text, View } from "react-native";
 
const assignments = [
  { id: "1", title: "Math Homework" },
  { id: "2", title: "Science Project" },
  { id: "3", title: "History Essay" },
];
 
export default function AssignmentsScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Upcoming Assignments đź“‘</Text>
      {assignments.map((assignment) => (
        <Pressable
          key={assignment.id}
          style={styles.item}
          onPress={() => router.push(`/assignments/${assignment.id}`)}
        >
          <Text style={styles.itemText}>{assignment.title}</Text>
        </Pressable>
      ))}
      <Button
        title="Add Assignment"
        onPress={() => router.push("/add-assignment")} // Opens our modal
      />
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    padding: 20,
  },
  title: { fontSize: 24, marginBottom: 20, color: "grey" },
  item: {
    marginVertical: 10,
    padding: 10,
    backgroundColor: "grey",
    width: "100%",
    borderRadius: 8,
  },
  itemText: { fontSize: 18, color: "blue" },
});

Then start your app:

npm run start

Route Groups for Organization

What are Route Groups?

Route groups let you organize related screens without changing the URL structure—useful for auth flows, overlays, or shared routes. For example, like grouping all login pages in a secret folder.

Auth Flow Example

app/
├── (auth)/
│   ├── login.tsx     # Sign-in screen
│   └── register.tsx  # Sign-up screen
└── ...

On the app/(auth)/register.tsx file:

import { router } from "expo-router";
import { Button, StyleSheet, Text, View } from "react-native";
 
export default function Register() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>📝 Register for Student Mate</Text>
      <Button title="Go to Login" onPress={() => router.push("/login")} />
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
  title: { fontSize: 24, marginBottom: 20 },
});

On the app/(auth)/login.tsx file:

import { View, Text, Button, StyleSheet } from "react-native";
import { router } from "expo-router";
 
export default function Login() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>đź”’ Login to Student Mate</Text>
      <Button title="Go to Register" onPress={() => router.push("/register")} />
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
  title: { fontSize: 24, marginBottom: 20 },
});

On the row 10 highlighted, check how the router is redirecting to /register without the need of the (auth) route group.

Now, let’s change what is the first screen when the App loads:

Creates a new file index.tsx on app/:

import { Redirect } from "expo-router";
 
export default function Index() {
  return <Redirect href="/login" />;
}

Rename the app/(tabs)/index.tsx to app/(tabs)/home.tsx. Then, update login.tsx:

import { View, Text, Button, StyleSheet } from "react-native";
import { router } from "expo-router";
 
export default function Login() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>đź”’ Login to Student Mate</Text>
      <Button title="Go to Home" onPress={() => router.push("/home")} />
      <Button title="Go to Register" onPress={() => router.push("/register")} />
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
  title: { fontSize: 24, marginBottom: 20 },
});

Great, when the App loads we are redirected to the Login screen. Very simple but just to show how you can organize your files into groups without changing the routes!

What We Learned in Part 2

  1. Stacks in Tabs: Like pages inside chapters—gives depth without losing the main tab.
  2. Modals: Floating panels for quick tasks—open with router.push, close with router.back.
  3. Route Groups: Invisible folders for organizing code—no impact on URLs.

What’s Next

In Part 3, we’ll learn how to:

The journey of a thousand miles begins with a single step. – Lao Tzu

Keep exploring, stay curious, and happy coding! đź’™