agentsclimarketplace

Navigation

Skill Shankulkarni/react-native-ai-plugin/plugins/rn-ai/skills/navigation

Use when working with Expo Router -- layouts, auth guards, typed routes, deep linking, tabs, modals, stack navigation, and passing params.From its SKILL.md

Install
npx -y skills add Shankulkarni/react-native-ai-plugin --skill navigation

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.7 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it

Navigation -- Expo Router v4

File-based routing. Every file in app/ is a route. Never string-concatenate paths.

Layout Groups

app/
├── _layout.tsx          ← root layout (providers, fonts, theme)
├── (auth)/              ← guest-only group
│   ├── _layout.tsx      ← redirects logged-in users to (app)
│   ├── login.tsx
│   └── register.tsx
├── (app)/               ← protected group
│   ├── _layout.tsx      ← redirects guests to (auth)/login
│   └── (tabs)/
│       ├── _layout.tsx  ← tab bar
│       ├── index.tsx    ← Home tab
│       └── profile.tsx  ← Profile tab
└── +not-found.tsx

Auth Guard Layouts

app/(auth)/_layout.tsx -- guest only:

import { Redirect, Stack } from 'expo-router'
import { useSession } from '@/hooks/use-session'

export default function AuthLayout() {
  const { session } = useSession()
  if (session) return <Redirect href="/(app)/(tabs)/" />
  return <Stack screenOptions={{ headerShown: false }} />
}

app/(app)/_layout.tsx -- protected:

import { Redirect, Stack } from 'expo-router'
import { useSession } from '@/hooks/use-session'

export default function AppLayout() {
  const { session } = useSession()
  if (!session) return <Redirect href="/(auth)/login" />
  return <Stack screenOptions={{ headerShown: false }} />
}

Tab Navigator

app/(app)/(tabs)/_layout.tsx:

import { Tabs } from 'expo-router'
import { HomeIcon, UserIcon } from 'lucide-react-native'

export default function TabLayout() {
  return (
    <Tabs screenOptions={{ tabBarActiveTintColor: '#6366f1', headerShown: false }}>
      <Tabs.Screen
        name="index"
        options={{ title: 'Home', tabBarIcon: ({ color }) => <HomeIcon color={color} size={24} /> }}
      />
      <Tabs.Screen
        name="profile"
        options={{ title: 'Profile', tabBarIcon: ({ color }) => <UserIcon color={color} size={24} /> }}
      />
    </Tabs>
  )
}

Stack with Header

<Stack>
  <Stack.Screen name="index" options={{ title: 'Home' }} />
  <Stack.Screen name="[id]" options={{ title: 'Detail', headerBackTitle: 'Back' }} />
</Stack>

Typed Routes

Enable in app.json:

{ "expo": { "experiments": { "typedRoutes": true } } }
import { router, Link } from 'expo-router'

// ✅ Typed -- compile-time checked
router.push({ pathname: '/(app)/(tabs)/', params: { tab: 'home' } })
router.push({ pathname: '/(app)/[id]', params: { id: item.id } })

<Link href={{ pathname: '/(app)/[id]', params: { id: item.id } }}>
  Open
</Link>

// ❌ Never string concatenation
router.push(`/items/${item.id}`)

Reading Params

import { useLocalSearchParams, useGlobalSearchParams } from 'expo-router'

// Local -- only params from current route segment
const { id } = useLocalSearchParams<{ id: string }>()

// Global -- params from any segment in the URL
const { tab } = useGlobalSearchParams<{ tab: string }>()

Modal

// app/(app)/modal.tsx
import { router } from 'expo-router'
import { View, Text } from 'react-native'
import { Pressable } from 'react-native'

export default function Modal() {
  return (
    <View className="flex-1 items-center justify-center">
      <Text className="text-lg font-semibold">Modal</Text>
      <Pressable onPress={() => router.back()}>
        <Text>Close</Text>
      </Pressable>
    </View>
  )
}

// In _layout.tsx
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />

Deep Linking

app.json:

{
  "expo": {
    "scheme": "myapp",
    "ios": { "associatedDomains": ["applinks:myapp.com"] },
    "android": { "intentFilters": [{ "action": "VIEW", "data": [{ "scheme": "myapp" }], "category": ["BROWSABLE", "DEFAULT"] }] }
  }
}

Expo Router handles deep links automatically based on file structure -- no extra config needed.

Imperative Navigation

import { router } from 'expo-router'

router.push({ pathname: '/(app)/[id]', params: { id } })
router.replace({ pathname: '/(auth)/login' })
router.back()
router.canGoBack()  // check before calling back()

Not Found

app/+not-found.tsx:

import { Link, Stack } from 'expo-router'
import { View, Text } from 'react-native'

export default function NotFound() {
  return (
    <>
      <Stack.Screen options={{ title: 'Not Found' }} />
      <View className="flex-1 items-center justify-center gap-4">
        <Text className="text-xl font-bold">Page not found</Text>
        <Link href="/(app)/(tabs)/" className="text-primary">Go home</Link>
      </View>
    </>
  )
}

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.