expo-router-patterns

Expo Router Patterns Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "expo-router-patterns" with this command: npx skills add ivantorresedge/molcajete.ai/ivantorresedge-molcajete-ai-expo-router-patterns

Expo Router Patterns Skill

This skill covers Expo Router navigation for React Native.

When to Use

Use this skill when:

  • Setting up navigation

  • Creating screens and routes

  • Implementing deep linking

  • Protecting routes

Core Principle

FILE-BASED ROUTING - Routes are defined by file structure (like Next.js).

File Structure

app/ ├── (auth)/ # Route group (not in URL) │ ├── login.tsx # /login │ ├── register.tsx # /register │ └── _layout.tsx # Layout for auth routes ├── (tabs)/ # Tab navigation group │ ├── _layout.tsx # Tabs layout │ ├── index.tsx # / │ └── profile.tsx # /profile ├── settings/ │ ├── index.tsx # /settings │ └── [id].tsx # /settings/123 (dynamic) ├── _layout.tsx # Root layout ├── +not-found.tsx # 404 page └── [...missing].tsx # Catch-all route

Basic Navigation

Link Component

import { Link } from 'expo-router';

<Link href="/profile">Go to Profile</Link>

// With params <Link href={{ pathname: '/user/[id]', params: { id: '123' }, }}

View User </Link>

// As child (for custom styling) <Link href="/settings" asChild> <TouchableOpacity> <Text>Settings</Text> </TouchableOpacity> </Link>

useRouter Hook

import { useRouter } from 'expo-router';

function Component(): React.ReactElement { const router = useRouter();

const handleNavigate = () => { // Push new screen router.push('/profile');

// Replace current screen
router.replace('/login');

// Go back
router.back();

// Navigate with params
router.push({
  pathname: '/user/[id]',
  params: { id: '123' },
});

};

return <Button onPress={handleNavigate}>Navigate</Button>; }

Layout Components

Root Layout

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

export default function RootLayout(): React.ReactElement { return ( <> <StatusBar style="auto" /> <Stack> <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack.Screen name="(auth)" options={{ headerShown: false }} /> <Stack.Screen name="modal" options={{ presentation: 'modal', headerTitle: 'Modal', }} /> </Stack> </> ); }

Tab Layout

// app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; import { Ionicons } from '@expo/vector-icons';

export default function TabLayout(): React.ReactElement { return ( <Tabs screenOptions={{ tabBarActiveTintColor: '#3B82F6', tabBarInactiveTintColor: '#6B7280', headerShown: false, }} > <Tabs.Screen name="index" options={{ title: 'Home', tabBarIcon: ({ color, size }) => ( <Ionicons name="home" size={size} color={color} /> ), }} /> <Tabs.Screen name="search" options={{ title: 'Search', tabBarIcon: ({ color, size }) => ( <Ionicons name="search" size={size} color={color} /> ), }} /> <Tabs.Screen name="profile" options={{ title: 'Profile', tabBarIcon: ({ color, size }) => ( <Ionicons name="person" size={size} color={color} /> ), }} /> </Tabs> ); }

Dynamic Routes

Single Parameter

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

export default function UserPage(): React.ReactElement { const { id } = useLocalSearchParams<{ id: string }>();

return ( <View className="flex-1 items-center justify-center"> <Text className="text-lg">User ID: {id}</Text> </View> ); }

Multiple Parameters

// app/[category]/[id].tsx import { useLocalSearchParams } from 'expo-router';

export default function ProductPage(): React.ReactElement { const { category, id } = useLocalSearchParams<{ category: string; id: string; }>();

return ( <View> <Text>Category: {category}</Text> <Text>Product ID: {id}</Text> </View> ); }

Catch-All Route

// app/[...path].tsx import { useLocalSearchParams } from 'expo-router';

export default function CatchAllPage(): React.ReactElement { const { path } = useLocalSearchParams<{ path: string[] }>();

return ( <View> <Text>Path segments: {path?.join('/')}</Text> </View> ); }

Protected Routes

// app/_layout.tsx import { Redirect, Stack } from 'expo-router'; import { useAuth } from '@/hooks/useAuth'; import { Loading } from '@/components/Loading';

export default function RootLayout(): React.ReactElement { const { user, isLoading } = useAuth();

if (isLoading) { return <Loading />; }

if (!user) { return <Redirect href="/login" />; }

return <Stack />; }

Route Groups

app/ ├── (auth)/ # Auth group (no /auth in URL) │ ├── login.tsx # /login │ └── register.tsx # /register ├── (app)/ # App group (no /app in URL) │ ├── home.tsx # /home │ └── profile.tsx # /profile

Modal Routes

// app/_layout.tsx <Stack> <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack.Screen name="modal" options={{ presentation: 'modal', animation: 'slide_from_bottom', }} /> </Stack>

// Navigate to modal router.push('/modal');

// Close modal router.back();

Deep Linking

Configuration

// app.json { "expo": { "scheme": "myapp", "web": { "bundler": "metro" } } }

Links

myapp:// # Opens app myapp://profile # Opens /profile myapp://user/123 # Opens /user/123 https://myapp.com/profile # Universal link

Navigation Hooks

usePathname

import { usePathname } from 'expo-router';

function Component(): React.ReactElement { const pathname = usePathname(); // Returns: "/user/123"

return <Text>Current path: {pathname}</Text>; }

useSegments

import { useSegments } from 'expo-router';

function Component(): React.ReactElement { const segments = useSegments(); // Returns: ["user", "123"]

return <Text>Segments: {segments.join(', ')}</Text>; }

useFocusEffect

import { useFocusEffect } from 'expo-router'; import { useCallback } from 'react';

function Screen(): React.ReactElement { useFocusEffect( useCallback(() => { // Runs when screen is focused console.log('Screen focused');

  return () => {
    // Cleanup when screen loses focus
    console.log('Screen unfocused');
  };
}, [])

);

return <View />; }

Notes

  • Use route groups (name) to organize without affecting URLs

  • Layouts cascade (parent layouts wrap children)

  • _layout.tsx defines the navigation structure

  • +not-found.tsx handles 404 routes

  • Deep linking is configured automatically

  • Use typed params with generics for type safety

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

software-principles

No summary provided by upstream source.

Repository SourceNeeds Review
General

react-components

No summary provided by upstream source.

Repository SourceNeeds Review
General

react-testing

No summary provided by upstream source.

Repository SourceNeeds Review
General

reanimated-patterns

No summary provided by upstream source.

Repository SourceNeeds Review