Typescript
react native
mobile development
expo
Expo Router
React Navigation
When you start developing with React Native, one of the first challenges is understanding how screen navigation works. In mobile apps, moving from one view to another is not a minor detail—it defines the entire user experience structure.
Unlike web development, where a link changes the page and the browser maintains the history, in a mobile app everything happens within a single application. That’s why we need a navigation library that manages that history internally, controls transitions, and allows passing parameters between screens.
Expo Router is part of the latest versions of Expo. Its philosophy is simple: the folder structure defines your navigation. Each file inside the app/
directory automatically becomes a route or screen.
1app/ 2 ├─ index.tsx → Main screen (/) 3 ├─ profile.tsx → Profile screen (/profile) 4 ├─ settings/ 5 │ └─ index.tsx → Settings screen (/settings) 6 └─ _layout.tsx → Common layout (Stack or Tab navigation)
You don’t need to manually register each screen or define routes in a central file. This makes the code more predictable, readable, and easy to scale.
The Stack pattern is probably the most common. Imagine a set of screens stacked on top of each other: when you navigate to a new view, it’s “pushed” onto the previous one; going back “pops” it off the stack.
1// app/_layout.tsx 2import { Stack } from "expo-router"; 3 4export default function Layout() { 5 return ( 6 <Stack> 7 <Stack.Screen name="index" options={{ title: "Home" }} /> 8 <Stack.Screen name="profile" options={{ title: "Profile" }} /> 9 </Stack> 10 ); 11}
Each file inside app/
becomes a stack screen. This makes hierarchical flows easy, like going from a list to a detail view.
1// app/(tabs)/_layout.tsx 2import { Tabs } from "expo-router"; 3 4export default function Layout() { 5 return ( 6 <Tabs> 7 <Tabs.Screen name="home" options={{ title: "Home" }} /> 8 <Tabs.Screen name="settings" options={{ title: "Settings" }} /> 9 </Tabs> 10 ); 11}
Typical structure:
1app/ 2 └─ (tabs)/ 3 ├─ _layout.tsx 4 ├─ home.tsx 5 └─ settings.tsx
One of the most common needs is sending information from one view to another. For example, when clicking a user, you want to open their profile and show their data.
With Expo Router, this is done declaratively:
1<Link href={{ pathname: "/profile", params: { userId: 42 } }}> 2 View Profile 3</Link>
1import { useLocalSearchParams } from "expo-router"; 2 3export default function ProfileScreen() { 4 const { userId } = useLocalSearchParams(); 5 return <Text>User profile {userId}</Text>; 6}
There’s no magic; parameters are read with a hook, just like you would with props or query params in React. This way of communicating keeps the app modular and easy to debug.
One of the biggest advantages of working with Expo and React Native in TypeScript is the ability to type your routes. This prevents common errors like passing incorrect parameters or misspelling a screen name.
You can define a global route type:
1export type RootStackParamList = { 2 index: undefined; 3 profile: { userId: number }; 4};
1<Link href={{ pathname: "/profile", params: { userId: 42 } }} />
If you try to pass a string instead of a number, the compiler will flag it as an error. For those coming from strongly typed languages like Kotlin or Swift, this feels natural: strong typing brings safety and confidence to your code.
And if you come from the native world (Kotlin or Swift), you’ll notice something familiar: each view is still an independent unit, but now with the flexibility of React and the power of JavaScript.