React Native Intermediate: Navigation, Data and Device Features
Navigation with Expo Router
In this lesson you will
- Explain how files in the app folder become screens
- Set up a Stack layout and move between screens with Link and router
- Build a dynamic route that reads a parameter
Introduction
Almost no app has only one screen. You need a way to go from a list to a detail page, and back. In the basics course you built single screens. This lesson adds navigation with Expo Router, the router that comes with every new Expo project and that Expo recommends for new apps.
You should have finished the React Native Basics course, or be comfortable with components, state and lists.
Files become screens
Expo Router is file-based. The docs state the rule: "When a file is added to the app directory, the file automatically becomes a route in your navigation." Each file in the folder exports one screen as its default export.
In a new project that folder is src/app (older projects used app at the root). A project might look like this:
src/app/
_layout.tsx sets up how screens are arranged
index.tsx the home screen, at "/"
about.tsx the about screen, at "/about"
posts/
index.tsx the list of posts, at "/posts"
[id].tsx one post, at "/posts/7" or "/posts/hello"Three things to notice:
- The file path is the screen's address.
about.tsxis/about. index.tsxis the default screen of its folder. You never set a "first screen" in code, because/opensindex.tsx.- Anything in square brackets, like
[id].tsx, is a dynamic route: one file that serves many addresses.
Keep components, hooks and helper code outside src/app, for example in src/components. Expo Router treats every file inside src/app as a route.
Quick quiz
Which file serves the screen at the address /about?
The layout: setting up a stack
_layout.tsx runs before the screens in its folder and decides how they are arranged. The most common arrangement is a stack: screens pile on top of each other, a new screen slides in, and the back button or swipe returns you.
React (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 us" }} />
</Stack>
);
}Stack draws a header with a title and a back button for you. Stack.Screen customises one screen by its file name, here setting the header title. Screens you do not list still work and get a default title.
Moving between screens
There are two ways to navigate.
Link is a component, like a link on a web page:
React (TSX)
import { Link } from "expo-router";
import { StyleSheet, Text, View } from "react-native";
export default function Home() {
return (
<View style={styles.screen}>
<Text style={styles.title}>Home</Text>
<Link href="/about">Go to About</Link>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, alignItems: "center", justifyContent: "center", gap: 12 },
title: { fontSize: 24, fontWeight: "bold" },
});router is a set of functions you call from code, for example after a button press or when a form finishes:
React (TSX)
import { router } from "expo-router";
import { Pressable, Text } from "react-native";
export default function Done() {
return (
<>
<Pressable onPress={() => router.push("/posts")}>
<Text>See posts</Text>
</Pressable>
<Pressable onPress={() => router.back()}>
<Text>Go back</Text>
</Pressable>
<Pressable onPress={() => router.replace("/")}>
<Text>Start over</Text>
</Pressable>
</>
);
}router.push(path)goes to a new screen and keeps the old one underneath, so the user can go back.router.back()goes back one screen.router.replace(path)swaps the current screen for another, so the user cannot go back to it. Use it after a login.
Dynamic routes and parameters
A list of posts needs a screen for each post, but you do not want to write a file for every one. A dynamic route does it with one file. Name the file [id].tsx and read the value from the address with useLocalSearchParams:
React (TSX)
import { Stack, useLocalSearchParams } from "expo-router";
import { StyleSheet, Text, View } from "react-native";
export default function PostScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
return (
<View style={styles.screen}>
<Stack.Screen options={{ title: `Post ${id}` }} />
<Text style={styles.heading}>Showing post number {id}</Text>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 24 },
heading: { fontSize: 20 },
});Visiting /posts/7 renders this screen with id equal to "7". Note that parameters from the address are always strings, so convert with Number(id) if you need a number. Putting Stack.Screen inside a screen is a handy way to set that screen's header from the data it already has.
You link to a dynamic screen by filling in the address, or by passing a pathname and parameters:
React (TSX)
import { Link } from "expo-router";
import { FlatList } from "react-native";
const POSTS = [
{ id: "1", title: "Hello Expo" },
{ id: "2", title: "Files are screens" },
];
export default function PostsIndex() {
return (
<FlatList
data={POSTS}
keyExtractor={(post) => post.id}
renderItem={({ item }) => (
<Link href={{ pathname: "/posts/[id]", params: { id: item.id } }} style={{ padding: 16, fontSize: 18 }}>
{item.title}
</Link>
)}
/>
);
}Quick quiz
In a screen at src/app/posts/[id].tsx, how do you read the id from the address?
When something is not found
Add a file called +not-found.tsx in src/app and it becomes the screen for any address that does not match a route. It is the place for a friendly "this page does not exist" message with a Link back home.
Why this matters beyond one app
Every screen has an address, so every screen can be deep linked: a link in an email or a notification can open your app straight on /posts/7. File-based routes give you that for free.
Try it yourself
Make a small app with a home screen listing three items. Add a dynamic route src/app/items/[id].tsx that shows the chosen item's name in the header and the body. Add a button on the detail screen that calls router.back().
Summary
- With Expo Router each file in
src/appis a screen,index.tsxis a folder's default screen, and[id].tsxis a dynamic route. _layout.tsxsets up how screens are arranged, such as aStackwith a header and back button.- Move with
Linkor withrouter.push,router.backandrouter.replace, and read route parameters, which are always strings, withuseLocalSearchParams.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.