Claude Code Plugins

Community-maintained marketplace

Feedback

expo-router-patterns

@IvanTorresEdge/molcajete.ai
0
0

Expo Router file-based navigation patterns. Use when implementing navigation.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name expo-router-patterns
description Expo Router file-based navigation patterns. Use when implementing navigation.

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