Skip to main content

Module 6: Navigation with Expo Router

Expo Router Setup

From zero to file-based routing in minutes

🎯 Learning Objectives

  • Create a new Expo project with Expo Router pre-configured
  • Understand the app directory structure and file naming conventions
  • Create your first routes using the file system
  • Implement a root layout with proper configuration
  • Navigate between screens using Link and programmatic navigation
  • Pass and receive route parameters

Creating a New Project

The easiest way to get started with Expo Router is to create a new project using the Expo CLI. The template comes with everything pre-configured.

# Create a new project with Expo Router
npx create-expo-app@latest my-app

# Navigate into the project
cd my-app

# Start the development server
npx expo start

When you run create-expo-app, you'll get a project with Expo Router already set up. The default template includes:

  • Expo Router configured and ready to use
  • TypeScript support
  • Example tab navigation structure
  • Basic screens to get you started

πŸ’‘ Adding to an Existing Project

If you have an existing Expo project, you can add Expo Router manually:

# Install expo-router and dependencies
npx expo install expo-router expo-linking expo-constants expo-status-bar

Then update your package.json to set the entry point:

{
  "main": "expo-router/entry"
}

Verifying the Setup

After running npx expo start, scan the QR code with Expo Go (Android) or the Camera app (iOS). You should see the default app with tab navigation working.

βœ… Success Indicators

  • App loads without errors
  • Tab navigation appears at the bottom
  • Tapping tabs switches between screens
  • Metro bundler shows "Bundled" in terminal

Project Structure Overview

Let's examine the project structure that Expo Router expects:

my-app/
β”œβ”€β”€ app/                    # πŸ‘ˆ All your routes live here
β”‚   β”œβ”€β”€ _layout.tsx         # Root layout (wraps all screens)
β”‚   β”œβ”€β”€ index.tsx           # Home screen ("/")
β”‚   β”œβ”€β”€ (tabs)/             # Tab group
β”‚   β”‚   β”œβ”€β”€ _layout.tsx     # Tab navigator configuration
β”‚   β”‚   β”œβ”€β”€ index.tsx       # First tab screen
β”‚   β”‚   └── explore.tsx     # Second tab screen
β”‚   └── +not-found.tsx      # 404 screen
β”œβ”€β”€ assets/                 # Images, fonts, etc.
β”œβ”€β”€ components/             # Reusable components
β”œβ”€β”€ constants/              # App constants (colors, etc.)
β”œβ”€β”€ hooks/                  # Custom hooks
β”œβ”€β”€ app.json                # Expo configuration
β”œβ”€β”€ package.json
└── tsconfig.json

πŸ“– The Golden Rule

Every file in the app/ directory becomes a route. The file path determines the URL path. app/about.tsx becomes /about. app/settings/profile.tsx becomes /settings/profile.

File System β†’ Routes Files in app/ πŸ“ app/ β”œβ”€β”€ index.tsx β”œβ”€β”€ about.tsx β”œβ”€β”€ settings/ β”‚ β”œβ”€β”€ index.tsx β”‚ └── profile.tsx └── product/ └── [id].tsx becomes Available Routes / ← index.tsx /about ← about.tsx /settings ← settings/index.tsx /settings/profile ← profile.tsx /product/123 ← [id].tsx /product/abc ← [id].tsx

Special Files

Expo Router uses special file naming conventions:

πŸ“‹ Special Files Reference

File Purpose
_layout.tsx Defines the layout/navigator for a directory
index.tsx The default route for a directory (like index.html)
[param].tsx Dynamic route segment (captures URL parameter)
[...rest].tsx Catch-all route (captures multiple segments)
(group)/ Route group (organizes without affecting URL)
+not-found.tsx 404 error page for unmatched routes

File Naming Conventions

Understanding the naming conventions is key to mastering Expo Router. Let's explore each one:

Index Routes

index.tsx files are specialβ€”they render at the directory's path without adding to the URL:

app/index.tsx           β†’ /
app/settings/index.tsx  β†’ /settings
app/blog/index.tsx      β†’ /blog

Dynamic Routes

Square brackets create dynamic segments that capture URL parameters:

app/user/[id].tsx       β†’ /user/123, /user/456, /user/abc
app/post/[slug].tsx     β†’ /post/hello-world, /post/my-first-post
app/[category]/[id].tsx β†’ /electronics/123, /clothing/456
// app/user/[id].tsx
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';

export default function UserScreen() {
  // Access the dynamic parameter
  const { id } = useLocalSearchParams();
  
  return (
    <View>
      <Text>User ID: {id}</Text>
    </View>
  );
}

Catch-All Routes

The spread syntax [...param] captures multiple path segments:

app/docs/[...path].tsx  β†’ /docs/intro
                        β†’ /docs/api/overview
                        β†’ /docs/guide/getting-started/install
// app/docs/[...path].tsx
import { useLocalSearchParams } from 'expo-router';

export default function DocsScreen() {
  const { path } = useLocalSearchParams();
  // path will be an array: ['guide', 'getting-started', 'install']
  
  return <Text>Path: {Array.isArray(path) ? path.join('/') : path}</Text>;
}

Route Groups

Parentheses create groups that organize files without affecting the URL:

app/(tabs)/home.tsx     β†’ /home (not /tabs/home)
app/(auth)/login.tsx    β†’ /login (not /auth/login)
app/(marketing)/about.tsx β†’ /about

Groups are perfect for:

  • Organizing related screens (all tab screens, all auth screens)
  • Sharing layouts across routes
  • Separating public vs authenticated sections
flowchart LR
    subgraph Files["File Structure"]
        A["app/(tabs)/_layout.tsx"]
        B["app/(tabs)/index.tsx"]
        C["app/(tabs)/profile.tsx"]
        D["app/(auth)/login.tsx"]
        E["app/(auth)/signup.tsx"]
    end
    
    subgraph URLs["URL Routes"]
        U1["/"]
        U2["/profile"]
        U3["/login"]
        U4["/signup"]
    end
    
    B --> U1
    C --> U2
    D --> U3
    E --> U4
    
    style A fill:#fff3cd
    style U1 fill:#e8f5e9
    style U2 fill:#e8f5e9
    style U3 fill:#e3f2fd
    style U4 fill:#e3f2fd
                

⚠️ Group Layouts

Each group can have its own _layout.tsx. The (tabs) group might use a Tab navigator while (auth) might use a Stack. This is how you create different navigation structures for different parts of your app.

The Root Layout

The app/_layout.tsx file is the most important file in your navigation structure. It wraps your entire app and defines the root navigator.

// app/_layout.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" options={{ title: 'Home' }} />
      <Stack.Screen name="about" options={{ title: 'About' }} />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
    </Stack>
  );
}

Available Navigators

Expo Router provides three main navigator types:

🧭 Navigator Types

Navigator Import Use Case
Stack import { Stack } from 'expo-router' Hierarchical navigation (push/pop)
Tabs import { Tabs } from 'expo-router' Bottom tab navigation
Drawer import { Drawer } from 'expo-router/drawer' Side drawer navigation

A Practical Root Layout

Here's a more complete root layout with fonts, splash screen, and theme:

// app/_layout.tsx
import { useEffect } from 'react';
import { Stack } from 'expo-router';
import { useFonts } from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
import { StatusBar } from 'expo-status-bar';
import { useColorScheme } from 'react-native';
import { 
  DarkTheme, 
  DefaultTheme, 
  ThemeProvider 
} from '@react-navigation/native';

// Prevent splash screen from auto-hiding
SplashScreen.preventAutoHideAsync();

export default function RootLayout() {
  const colorScheme = useColorScheme();
  
  // Load custom fonts
  const [fontsLoaded] = useFonts({
    'SpaceMono': require('../assets/fonts/SpaceMono-Regular.ttf'),
  });

  useEffect(() => {
    if (fontsLoaded) {
      SplashScreen.hideAsync();
    }
  }, [fontsLoaded]);

  // Don't render until fonts are loaded
  if (!fontsLoaded) {
    return null;
  }

  return (
    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <Stack>
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
        <Stack.Screen name="+not-found" />
      </Stack>
      <StatusBar style="auto" />
    </ThemeProvider>
  );
}

βœ… Root Layout Best Practices

  • Load fonts and other assets here
  • Set up theme providers
  • Configure splash screen behavior
  • Add global providers (auth context, state management)
  • Set up status bar style

Screen Options

Each screen can be configured with options:

// In the layout file
<Stack.Screen 
  name="profile" 
  options={{
    title: 'My Profile',           // Header title
    headerShown: true,             // Show/hide header
    headerBackTitle: 'Back',       // iOS back button text
    presentation: 'modal',         // Present as modal
    animation: 'slide_from_right', // Transition animation
    headerStyle: {
      backgroundColor: '#f4511e',
    },
    headerTintColor: '#fff',
    headerTitleStyle: {
      fontWeight: 'bold',
    },
  }} 
/>

// Or inside the screen component itself
import { Stack } from 'expo-router';

export default function ProfileScreen() {
  return (
    <>
      <Stack.Screen 
        options={{ 
          title: 'Profile',
          headerRight: () => <SettingsButton />,
        }} 
      />
      <View>{/* Screen content */}</View>
    </>
  );
}

Creating Routes

Let's build a simple app structure to practice route creation. We'll create:

  • A home screen
  • An about screen
  • A products listing
  • A dynamic product detail screen
app/
β”œβ”€β”€ _layout.tsx
β”œβ”€β”€ index.tsx          # Home (/)
β”œβ”€β”€ about.tsx          # About (/about)
└── product/
    β”œβ”€β”€ index.tsx      # Product listing (/product)
    └── [id].tsx       # Product detail (/product/123)

Step 1: Root Layout

// app/_layout.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: {
          backgroundColor: '#6366f1',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}
    >
      <Stack.Screen name="index" options={{ title: 'Home' }} />
      <Stack.Screen name="about" options={{ title: 'About Us' }} />
      <Stack.Screen 
        name="product/index" 
        options={{ title: 'Products' }} 
      />
      <Stack.Screen 
        name="product/[id]" 
        options={{ title: 'Product Details' }} 
      />
    </Stack>
  );
}

Step 2: Home Screen

// app/index.tsx
import { View, Text, StyleSheet } from 'react-native';
import { Link } from 'expo-router';

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome Home!</Text>
      <Text style={styles.subtitle}>Explore our app</Text>
      
      <View style={styles.links}>
        <Link href="/about" style={styles.link}>
          About Us β†’
        </Link>
        
        <Link href="/product" style={styles.link}>
          Browse Products β†’
        </Link>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 18,
    color: '#666',
    marginBottom: 32,
  },
  links: {
    gap: 16,
  },
  link: {
    fontSize: 18,
    color: '#6366f1',
    paddingVertical: 12,
  },
});

Step 3: About Screen

// app/about.tsx
import { View, Text, StyleSheet } from 'react-native';

export default function AboutScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>About Us</Text>
      <Text style={styles.paragraph}>
        We're building amazing mobile experiences with React Native 
        and Expo Router. This is a demonstration of file-based routing.
      </Text>
      <Text style={styles.paragraph}>
        Notice how the URL changed to /about? That happened automatically
        because this file is named about.tsx!
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    marginBottom: 16,
  },
  paragraph: {
    fontSize: 16,
    lineHeight: 24,
    color: '#333',
    marginBottom: 16,
  },
});

Step 4: Product Listing

// app/product/index.tsx
import { View, Text, FlatList, Pressable, StyleSheet } from 'react-native';
import { Link } from 'expo-router';

const products = [
  { id: '1', name: 'Wireless Headphones', price: 99.99 },
  { id: '2', name: 'Smart Watch', price: 299.99 },
  { id: '3', name: 'Laptop Stand', price: 49.99 },
  { id: '4', name: 'Mechanical Keyboard', price: 149.99 },
  { id: '5', name: 'USB-C Hub', price: 79.99 },
];

export default function ProductListScreen() {
  return (
    <View style={styles.container}>
      <FlatList
        data={products}
        keyExtractor={(item) => item.id}
        contentContainerStyle={styles.list}
        renderItem={({ item }) => (
          <Link href={`/product/${item.id}`} asChild>
            <Pressable style={styles.productCard}>
              <Text style={styles.productName}>{item.name}</Text>
              <Text style={styles.productPrice}>${item.price}</Text>
            </Pressable>
          </Link>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  list: {
    padding: 16,
  },
  productCard: {
    backgroundColor: '#fff',
    padding: 16,
    borderRadius: 8,
    marginBottom: 12,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
    elevation: 2,
  },
  productName: {
    fontSize: 16,
    fontWeight: '500',
  },
  productPrice: {
    fontSize: 16,
    color: '#6366f1',
    fontWeight: 'bold',
  },
});

Step 5: Product Detail (Dynamic Route)

// app/product/[id].tsx
import { View, Text, StyleSheet } from 'react-native';
import { useLocalSearchParams, Stack } from 'expo-router';

// In a real app, you'd fetch this from an API
const products: Record<string, { name: string; price: number; description: string }> = {
  '1': { 
    name: 'Wireless Headphones', 
    price: 99.99,
    description: 'Premium noise-canceling wireless headphones with 30-hour battery life.'
  },
  '2': { 
    name: 'Smart Watch', 
    price: 299.99,
    description: 'Feature-packed smartwatch with health monitoring and GPS.'
  },
  '3': { 
    name: 'Laptop Stand', 
    price: 49.99,
    description: 'Ergonomic aluminum laptop stand for better posture.'
  },
  '4': { 
    name: 'Mechanical Keyboard', 
    price: 149.99,
    description: 'RGB mechanical keyboard with hot-swappable switches.'
  },
  '5': { 
    name: 'USB-C Hub', 
    price: 79.99,
    description: '7-in-1 USB-C hub with HDMI, USB-A, and card reader.'
  },
};

export default function ProductDetailScreen() {
  const { id } = useLocalSearchParams<{ id: string }>();
  const product = products[id];

  if (!product) {
    return (
      <View style={styles.container}>
        <Text style={styles.error}>Product not found</Text>
      </View>
    );
  }

  return (
    <>
      {/* Dynamically set the header title */}
      <Stack.Screen options={{ title: product.name }} />
      
      <View style={styles.container}>
        <View style={styles.imageContainer}>
          <Text style={styles.imagePlaceholder}>πŸ“¦</Text>
        </View>
        
        <View style={styles.content}>
          <Text style={styles.name}>{product.name}</Text>
          <Text style={styles.price}>${product.price}</Text>
          <Text style={styles.description}>{product.description}</Text>
          
          <View style={styles.badge}>
            <Text style={styles.badgeText}>Product ID: {id}</Text>
          </View>
        </View>
      </View>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  imageContainer: {
    height: 250,
    backgroundColor: '#f0f0f0',
    justifyContent: 'center',
    alignItems: 'center',
  },
  imagePlaceholder: {
    fontSize: 80,
  },
  content: {
    padding: 24,
  },
  name: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  price: {
    fontSize: 28,
    color: '#6366f1',
    fontWeight: 'bold',
    marginBottom: 16,
  },
  description: {
    fontSize: 16,
    lineHeight: 24,
    color: '#666',
    marginBottom: 24,
  },
  badge: {
    backgroundColor: '#e0e7ff',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
    alignSelf: 'flex-start',
  },
  badgeText: {
    color: '#6366f1',
    fontSize: 12,
    fontWeight: '500',
  },
  error: {
    fontSize: 18,
    color: '#ef4444',
    textAlign: 'center',
    marginTop: 50,
  },
});

Route Parameters

Expo Router supports two types of parameters: path parameters and query parameters.

Path Parameters

Path parameters are part of the URL path and are captured using dynamic segments:

// File: app/user/[id].tsx
// URL: /user/123

import { useLocalSearchParams } from 'expo-router';

export default function UserScreen() {
  const { id } = useLocalSearchParams<{ id: string }>();
  
  return <Text>User ID: {id}</Text>; // "User ID: 123"
}

// File: app/blog/[year]/[month]/[slug].tsx
// URL: /blog/2024/01/my-first-post

export default function BlogPostScreen() {
  const { year, month, slug } = useLocalSearchParams<{
    year: string;
    month: string;
    slug: string;
  }>>();
  
  return (
    <Text>
      Post: {slug} from {month}/{year}
    </Text>
  );
}

Query Parameters

Query parameters are appended after ? in the URL:

// Navigate with query params
router.push('/search?query=shoes&sort=price&page=1');

// Or using the object syntax
router.push({
  pathname: '/search',
  params: {
    query: 'shoes',
    sort: 'price',
    page: '1',
  },
});

// Access in the target screen
// File: app/search.tsx
import { useLocalSearchParams } from 'expo-router';

export default function SearchScreen() {
  const { query, sort, page } = useLocalSearchParams<{
    query?: string;
    sort?: string;
    page?: string;
  }>>();

  return (
    <View>
      <Text>Searching for: {query}</Text>
      <Text>Sort by: {sort ?? 'relevance'}</Text>
      <Text>Page: {page ?? '1'}</Text>
    </View>
  );
}

Global vs Local Search Params

Expo Router provides two hooks for accessing parameters:

πŸ” Parameter Hooks

Hook Scope Use Case
useLocalSearchParams Current route only Most commonβ€”params for this screen
useGlobalSearchParams All routes in the URL Access params from parent routes
// URL: /shop/electronics/123?ref=homepage

// In app/shop/[category]/[id].tsx
const localParams = useLocalSearchParams();
// { category: 'electronics', id: '123', ref: 'homepage' }

const globalParams = useGlobalSearchParams();  
// Same in this case, but useful in nested navigators

TypeScript Support

For full type safety with routes and parameters:

// Define your route params in a central types file
// types/navigation.ts
export type RootStackParamList = {
  index: undefined;
  about: undefined;
  'product/index': undefined;
  'product/[id]': { id: string };
  search: { query?: string; sort?: string };
};

// Use with the hook
import { useLocalSearchParams } from 'expo-router';
import type { RootStackParamList } from '@/types/navigation';

export default function ProductScreen() {
  const { id } = useLocalSearchParams<RootStackParamList['product/[id]']>();
  // id is typed as string
}

βœ… Expo Router Typed Routes

Expo Router can generate types automatically! Run npx expo customize tsconfig.json and enable typed routes in your app.json:

{
  "expo": {
    "experiments": {
      "typedRoutes": true
    }
  }
}

Hands-On Exercises

Exercise 1: Create a Basic Navigation Structure

Create a new Expo project and set up the following routes:

  • / - Home screen with welcome message
  • /about - About screen
  • /contact - Contact screen
  • All screens should link to each other
βœ… Solution
app/
β”œβ”€β”€ _layout.tsx
β”œβ”€β”€ index.tsx
β”œβ”€β”€ about.tsx
└── contact.tsx
// app/_layout.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" options={{ title: 'Home' }} />
      <Stack.Screen name="about" options={{ title: 'About' }} />
      <Stack.Screen name="contact" options={{ title: 'Contact' }} />
    </Stack>
  );
}

// app/index.tsx
import { View, Text, StyleSheet } from 'react-native';
import { Link } from 'expo-router';

export default function Home() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome!</Text>
      <Link href="/about" style={styles.link}>About Us</Link>
      <Link href="/contact" style={styles.link}>Contact</Link>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20 },
  title: { fontSize: 24, marginBottom: 20 },
  link: { fontSize: 18, color: 'blue', marginVertical: 10 },
});

// app/about.tsx and app/contact.tsx follow the same pattern

Exercise 2: Dynamic Routes with Parameters

Extend your app with a dynamic blog route:

  • /blog - List of blog posts
  • /blog/[slug] - Individual blog post
  • Blog list should show at least 3 posts with links
  • Blog post screen should display the slug
βœ… Solution
// app/blog/index.tsx
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { Link } from 'expo-router';

const posts = [
  { slug: 'getting-started', title: 'Getting Started with Expo' },
  { slug: 'navigation-tips', title: 'Navigation Tips & Tricks' },
  { slug: 'styling-guide', title: 'Complete Styling Guide' },
];

export default function BlogList() {
  return (
    <FlatList
      data={posts}
      keyExtractor={(item) => item.slug}
      contentContainerStyle={styles.container}
      renderItem={({ item }) => (
        <Link href={`/blog/${item.slug}`} style={styles.link}>
          {item.title}
        </Link>
      )}
    />
  );
}

const styles = StyleSheet.create({
  container: { padding: 20 },
  link: { fontSize: 18, color: 'blue', paddingVertical: 12 },
});

// app/blog/[slug].tsx
import { View, Text, StyleSheet } from 'react-native';
import { useLocalSearchParams, Stack } from 'expo-router';

export default function BlogPost() {
  const { slug } = useLocalSearchParams<{ slug: string }>();

  return (
    <>
      <Stack.Screen options={{ title: slug?.replace(/-/g, ' ') }} />
      <View style={styles.container}>
        <Text style={styles.title}>Blog Post</Text>
        <Text>Slug: {slug}</Text>
      </View>
    </>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20 },
  title: { fontSize: 24, marginBottom: 12 },
});

Exercise 3: Programmatic Navigation

Create a simple form that navigates on submit:

  • Create a /feedback screen with a TextInput and Submit button
  • On submit, navigate to /feedback/thanks with the feedback as a query parameter
  • Display the submitted feedback on the thanks screen
βœ… Solution
// app/feedback/index.tsx
import { useState } from 'react';
import { View, Text, TextInput, Pressable, StyleSheet } from 'react-native';
import { useRouter } from 'expo-router';

export default function FeedbackForm() {
  const [feedback, setFeedback] = useState('');
  const router = useRouter();

  const handleSubmit = () => {
    if (feedback.trim()) {
      router.push({
        pathname: '/feedback/thanks',
        params: { message: feedback },
      });
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.label}>Your Feedback:</Text>
      <TextInput
        style={styles.input}
        value={feedback}
        onChangeText={setFeedback}
        multiline
        placeholder="Enter your feedback..."
      />
      <Pressable style={styles.button} onPress={handleSubmit}>
        <Text style={styles.buttonText}>Submit</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20 },
  label: { fontSize: 16, marginBottom: 8 },
  input: { 
    borderWidth: 1, 
    borderColor: '#ccc', 
    padding: 12, 
    borderRadius: 8,
    height: 120,
    textAlignVertical: 'top',
  },
  button: { 
    backgroundColor: '#6366f1', 
    padding: 16, 
    borderRadius: 8, 
    marginTop: 16 
  },
  buttonText: { color: 'white', textAlign: 'center', fontWeight: 'bold' },
});

// app/feedback/thanks.tsx
import { View, Text, StyleSheet } from 'react-native';
import { useLocalSearchParams, Link } from 'expo-router';

export default function Thanks() {
  const { message } = useLocalSearchParams<{ message?: string }>();

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Thank You! πŸŽ‰</Text>
      <Text style={styles.label}>Your feedback:</Text>
      <Text style={styles.feedback}>"{message}"</Text>
      <Link href="/" style={styles.link}>Back to Home</Link>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20, alignItems: 'center', justifyContent: 'center' },
  title: { fontSize: 28, fontWeight: 'bold', marginBottom: 20 },
  label: { fontSize: 14, color: '#666' },
  feedback: { fontSize: 18, fontStyle: 'italic', marginVertical: 12 },
  link: { fontSize: 16, color: '#6366f1', marginTop: 20 },
});

Summary

You now have Expo Router set up and understand the fundamentals of file-based routing. You can create routes, navigate between screens, and pass parameters.

🎯 Key Takeaways

  • Files = Routes: Every file in app/ becomes a route automatically
  • _layout.tsx: Defines navigators and wraps child routes
  • index.tsx: Renders at the directory path (like index.html)
  • [param].tsx: Creates dynamic routes that capture URL segments
  • (group)/: Organizes files without affecting URLs
  • Link component: Best for static navigation and lists
  • useRouter hook: Best for programmatic navigation after actions
  • useLocalSearchParams: Access route and query parameters

πŸ“ File Structure Cheat Sheet

app/
β”œβ”€β”€ _layout.tsx      # Navigator wrapper
β”œβ”€β”€ index.tsx        # "/" route
β”œβ”€β”€ about.tsx        # "/about" route
β”œβ”€β”€ [id].tsx         # "/:id" dynamic route
β”œβ”€β”€ [...rest].tsx    # Catch-all route
β”œβ”€β”€ +not-found.tsx   # 404 page
└── (group)/         # Route group (no URL impact)
    └── _layout.tsx  # Group's navigator

What's Next?

In the next lesson, we'll dive deep into Stack Navigationβ€”the most fundamental navigation pattern. You'll learn how to configure headers, handle transitions, pass data between screens, and implement common patterns like master-detail views.