Overview
CryptoTracker uses Expo Router for navigation, which implements file-based routing similar to Next.js. Routes are automatically created based on the file structure in the app/ directory.
Expo Router is built on top of React Navigation, providing type-safe routing with automatic deep linking support.
Route Structure
The application’s routing architecture is defined by the following file structure:
app/
├── _layout.tsx # Root layout (Stack navigator)
├── (tabs)/ # Tab navigation group
│ ├── _layout.tsx # Tab bar configuration
│ └── index.tsx # Home route → /
├── crypto/
│ └── [id].tsx # Dynamic route → /crypto/:id
├── +not-found.tsx # 404 page
└── +html.tsx # Custom HTML wrapper (web)
Route Mapping
File Path URL Screen (tabs)/index.tsx/Home screen with crypto list crypto/[id].tsx/crypto/90Detail page for Bitcoin +not-found.tsxAny invalid route 404 error page
Root Layout
The root layout (app/_layout.tsx) sets up the base navigation structure and theme configuration:
function RootLayoutNav () {
const colorScheme = useColorScheme ();
return (
< ThemeProvider value = { colorScheme === 'dark' ? DarkTheme : DefaultTheme } >
< Stack >
< Stack.Screen name = "(tabs)" options = { { headerShown: false } } />
< Stack.Screen name = "modal" options = { { presentation: 'modal' } } />
</ Stack >
</ ThemeProvider >
);
}
Theme Provider : Wraps the app with React Navigation’s theme system
Color Scheme Detection : Automatically adapts to light/dark mode
Stack Navigator : Provides stack-based navigation with screen transitions
Font Loading : Loads custom fonts before rendering the app
Initial Route Configuration
export const unstable_settings = {
initialRouteName: '(tabs)' ,
};
This ensures the app always starts at the tab navigation group when launched.
Tab Navigation
The tab layout (app/(tabs)/_layout.tsx) creates a bottom tab bar with the FilterProvider context:
app/(tabs)/_layout.tsx:34-55
export default function TabLayout () {
const colorScheme = useColorScheme ();
return (
< FilterProvider >
< Tabs
screenOptions = { {
tabBarActiveTintColor: Colors [ colorScheme ?? 'light' ]. tint ,
} }
>
< Tabs.Screen
name = "index"
options = { {
title: 'Inicio' ,
tabBarIcon : ({ color }) => < TabBarIcon name = "home" color = { color } /> ,
header : () => < HomeHeader /> ,
} }
/>
</ Tabs >
</ FilterProvider >
);
}
The (tabs) directory uses parentheses to create a route group . This organizes routes without adding path segments to the URL.
Tab Screen Configuration
Screen Options
Custom Tab Icon
< Tabs.Screen
name = "index" // Route file name
options = { {
title: 'Inicio' , // Tab label
tabBarIcon : ({ color }) => < TabBarIcon name = "home" color = { color } /> ,
header : () => < HomeHeader /> , // Custom header component
} }
/>
app/(tabs)/_layout.tsx:18-23
function TabBarIcon ( props : {
name : React . ComponentProps < typeof FontAwesome >[ 'name' ];
color : string ;
}) {
return < FontAwesome size = { 22 } style = { { marginBottom: - 2 } } { ... props } /> ;
}
Dynamic Routes
The crypto detail page uses dynamic routing with the [id].tsx file naming convention:
app/crypto/[id].tsx:15-26
const CryptoDetailPage = () => {
const params = useLocalSearchParams ();
const id = Array . isArray ( params . id ) ? params . id [ 0 ] : params . id ;
if ( ! id ) {
return < Text > No se encontró la criptomoneda. </ Text > ;
}
return < CryptoDetailScreen id = { id } /> ;
};
How Dynamic Routes Work
URL Parameter Extraction
The useLocalSearchParams() hook extracts route parameters from the URL: const params = useLocalSearchParams ();
// For /crypto/90 → { id: '90' }
Type Safety
Parameters are handled safely to account for array or string values: const id = Array . isArray ( params . id ) ? params . id [ 0 ] : params . id ;
Validation
The component validates the parameter before rendering: if ( ! id ) {
return < Text > No se encontró la criptomoneda. </ Text > ;
}
Data Fetching
The ID is passed to the detail screen, which fetches the full crypto data: return < CryptoDetailScreen id = { id } /> ;
Navigation Between Screens
CryptoTracker uses the useRouter hook for programmatic navigation:
Navigation from CryptoCard
src/components/CryptoCard.tsx:21-40
const CryptoCard = ({ crypto } : Props ) => {
const router = useRouter ();
return (
< TouchableOpacity
onPress = { () =>
router . push ({
pathname: '/crypto/[id]' ,
params: { id: crypto . id || '' },
})
}
>
{ /* Card content */ }
</ TouchableOpacity >
);
};
router.push()
router.replace()
router.back()
Navigates to a new screen and adds it to the navigation stack: router . push ( '/crypto/90' );
// or with params object
router . push ({
pathname: '/crypto/[id]' ,
params: { id: '90' }
});
Replaces the current screen in the stack: router . replace ( '/crypto/90' );
Returns to the previous screen:
Type-Safe Navigation
Expo Router automatically generates TypeScript types for your routes:
import { Href } from 'expo-router' ;
const cryptoRoute : Href = '/crypto/[id]' ;
const homeRoute : Href = '/' ;
Deep Linking
Expo Router automatically handles deep linking. URLs are mapped directly to routes:
Deep Link Route File Screen myapp://(tabs)/index.tsxHome myapp://crypto/90crypto/[id].tsxBitcoin detail
Deep linking works automatically on both native platforms and web without additional configuration.
Route Groups
The (tabs) directory demonstrates route groups - a way to organize routes without affecting the URL structure:
app/
├── (tabs)/ # Group without URL segment
│ ├── _layout.tsx # Layout for this group
│ └── index.tsx # → /
Without parentheses, the route would be /tabs instead of /.
Benefits of Route Groups
URL Clean-up Keep URLs clean while organizing code logically
Shared Layouts Apply common layouts to grouped routes
Context Providers Wrap specific route groups with providers
Navigation Types Separate tabs, stacks, and drawers
Screens can define custom header components:
app/(tabs)/_layout.tsx:49
header : () => < HomeHeader />
This replaces the default navigation header with a custom component that includes search and filter functionality.
Navigation Flow Example
Here’s a complete navigation flow from home to detail:
User Opens App
App starts at root layout (_layout.tsx) which loads fonts and theme
Initial Route Rendered
Stack navigator shows (tabs) group, which renders index.tsx (home screen)
Home Screen Loads
index.tsx renders HomeScreen component, which fetches crypto data
User Taps Crypto Card
CryptoCard calls router.push({ pathname: '/crypto/[id]', params: { id: '90' } })
Navigation Transition
Stack navigator pushes crypto/[id].tsx onto the stack with slide animation
Detail Screen Renders
[id].tsx extracts ID from params and renders CryptoDetailScreen
User Navigates Back
Back button or gesture calls router.back(), returning to home screen
Best Practices
Always use TypeScript types for route parameters: import { useLocalSearchParams } from 'expo-router' ;
type Params = {
id : string ;
};
const params = useLocalSearchParams < Params >();
Validate Dynamic Parameters
Always validate dynamic route parameters: if ( ! id ) {
return < ErrorScreen message = "Invalid ID" /> ;
}
Use Route Groups for Organization
Group related routes together: app/
├── (auth)/
│ ├── login.tsx
│ └── register.tsx
├── (app)/
│ ├── home.tsx
│ └── profile.tsx
Leverage Layouts for Shared Logic
Use _layout.tsx files to share providers, headers, or authentication logic: export default function AuthLayout () {
return (
< AuthProvider >
< Stack />
</ AuthProvider >
);
}
Next Steps
Architecture Understand the overall app structure
State Management Learn about Context API and hooks