Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    speedoa1

    react-native

    speedoa1/react-native
    Coding
    6
    3 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    React Native and Expo development patterns and best practices

    SKILL.md

    React Native Skill

    Modern React Native development with Expo, navigation patterns, and native module integration.

    When to Use

    • Building cross-platform mobile applications
    • Setting up Expo projects
    • Implementing navigation patterns
    • Integrating native modules
    • Optimizing mobile performance

    Project Setup with Expo

    # Create new Expo project
    npx create-expo-app@latest my-app --template tabs
    
    # Or with blank template
    npx create-expo-app@latest my-app --template blank-typescript
    
    # Start development
    cd my-app
    npx expo start
    

    Project Structure

    my-app/
    ├── app/                    # Expo Router (file-based routing)
    │   ├── _layout.tsx         # Root layout
    │   ├── index.tsx           # Home screen
    │   ├── (tabs)/             # Tab group
    │   │   ├── _layout.tsx     # Tab layout
    │   │   ├── index.tsx       # First tab
    │   │   └── settings.tsx    # Settings tab
    │   └── [id].tsx            # Dynamic route
    ├── components/
    │   ├── ui/                 # Reusable UI components
    │   └── features/           # Feature-specific components
    ├── hooks/                  # Custom hooks
    ├── lib/                    # Utilities
    ├── constants/              # App constants
    ├── assets/                 # Static assets
    └── app.json               # Expo config
    

    Expo Router (Navigation)

    Root Layout

    // app/_layout.tsx
    import { Stack } from 'expo-router'
    import { StatusBar } from 'expo-status-bar'
    
    export default function RootLayout() {
      return (
        <>
          <StatusBar style="auto" />
          <Stack>
            <Stack.Screen name="index" options={{ title: 'Home' }} />
            <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
            <Stack.Screen 
              name="modal" 
              options={{ presentation: 'modal' }} 
            />
          </Stack>
        </>
      )
    }
    

    Tab Navigation

    // app/(tabs)/_layout.tsx
    import { Tabs } from 'expo-router'
    import { Ionicons } from '@expo/vector-icons'
    
    export default function TabLayout() {
      return (
        <Tabs screenOptions={{ tabBarActiveTintColor: '#007AFF' }}>
          <Tabs.Screen
            name="index"
            options={{
              title: 'Home',
              tabBarIcon: ({ color, size }) => (
                <Ionicons name="home" size={size} color={color} />
              ),
            }}
          />
          <Tabs.Screen
            name="settings"
            options={{
              title: 'Settings',
              tabBarIcon: ({ color, size }) => (
                <Ionicons name="settings" size={size} color={color} />
              ),
            }}
          />
        </Tabs>
      )
    }
    

    Navigation

    import { Link, useRouter, useLocalSearchParams } from 'expo-router'
    
    // Declarative navigation
    <Link href="/profile">Go to Profile</Link>
    <Link href={{ pathname: '/user/[id]', params: { id: '123' } }}>
      User 123
    </Link>
    
    // Imperative navigation
    const router = useRouter()
    router.push('/profile')
    router.replace('/home')
    router.back()
    
    // Get params
    const { id } = useLocalSearchParams<{ id: string }>()
    

    Styling Patterns

    StyleSheet

    import { StyleSheet, View, Text } from 'react-native'
    
    export function Card({ title, children }) {
      return (
        <View style={styles.card}>
          <Text style={styles.title}>{title}</Text>
          {children}
        </View>
      )
    }
    
    const styles = StyleSheet.create({
      card: {
        backgroundColor: '#fff',
        borderRadius: 12,
        padding: 16,
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.1,
        shadowRadius: 4,
        elevation: 3, // Android shadow
      },
      title: {
        fontSize: 18,
        fontWeight: '600',
        marginBottom: 8,
      },
    })
    

    NativeWind (Tailwind)

    npm install nativewind tailwindcss
    
    // With NativeWind
    import { View, Text } from 'react-native'
    
    export function Card({ title, children }) {
      return (
        <View className="bg-white rounded-xl p-4 shadow-md">
          <Text className="text-lg font-semibold mb-2">{title}</Text>
          {children}
        </View>
      )
    }
    

    Components & Patterns

    Safe Area Handling

    import { SafeAreaView, SafeAreaProvider } from 'react-native-safe-area-context'
    
    // In root
    export default function App() {
      return (
        <SafeAreaProvider>
          <RootLayout />
        </SafeAreaProvider>
      )
    }
    
    // In screens
    export function HomeScreen() {
      return (
        <SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
          <Content />
        </SafeAreaView>
      )
    }
    

    FlatList Performance

    import { FlatList } from 'react-native'
    import { useCallback } from 'react'
    
    export function UserList({ users }) {
      const renderItem = useCallback(
        ({ item }) => <UserCard user={item} />,
        []
      )
      
      const keyExtractor = useCallback(
        (item) => item.id.toString(),
        []
      )
    
      return (
        <FlatList
          data={users}
          renderItem={renderItem}
          keyExtractor={keyExtractor}
          // Performance optimizations
          removeClippedSubviews={true}
          maxToRenderPerBatch={10}
          windowSize={5}
          initialNumToRender={10}
          // Pull to refresh
          onRefresh={handleRefresh}
          refreshing={isRefreshing}
          // Infinite scroll
          onEndReached={loadMore}
          onEndReachedThreshold={0.5}
          ListFooterComponent={<LoadingFooter />}
          // Empty state
          ListEmptyComponent={<EmptyState />}
        />
      )
    }
    

    Keyboard Handling

    import {
      KeyboardAvoidingView,
      Platform,
      TouchableWithoutFeedback,
      Keyboard,
    } from 'react-native'
    
    export function FormScreen() {
      return (
        <KeyboardAvoidingView
          behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
          style={{ flex: 1 }}
        >
          <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
            <View style={{ flex: 1 }}>
              <TextInput placeholder="Email" />
              <TextInput placeholder="Password" secureTextEntry />
            </View>
          </TouchableWithoutFeedback>
        </KeyboardAvoidingView>
      )
    }
    

    State Management

    React Query for Server State

    import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'
    
    const queryClient = new QueryClient()
    
    // Provider
    export default function App() {
      return (
        <QueryClientProvider client={queryClient}>
          <RootLayout />
        </QueryClientProvider>
      )
    }
    
    // Usage
    export function UserProfile({ userId }) {
      const { data, isLoading, error } = useQuery({
        queryKey: ['user', userId],
        queryFn: () => fetchUser(userId),
      })
    
      const mutation = useMutation({
        mutationFn: updateUser,
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: ['user', userId] })
        },
      })
    
      if (isLoading) return <LoadingSpinner />
      if (error) return <ErrorView error={error} />
    
      return <UserView user={data} onUpdate={mutation.mutate} />
    }
    

    Zustand for Client State

    import { create } from 'zustand'
    import { persist, createJSONStorage } from 'zustand/middleware'
    import AsyncStorage from '@react-native-async-storage/async-storage'
    
    interface AuthStore {
      user: User | null
      token: string | null
      login: (user: User, token: string) => void
      logout: () => void
    }
    
    export const useAuthStore = create<AuthStore>()(
      persist(
        (set) => ({
          user: null,
          token: null,
          login: (user, token) => set({ user, token }),
          logout: () => set({ user: null, token: null }),
        }),
        {
          name: 'auth-storage',
          storage: createJSONStorage(() => AsyncStorage),
        }
      )
    )
    

    Native Modules & APIs

    Camera (Expo)

    import { CameraView, useCameraPermissions } from 'expo-camera'
    
    export function CameraScreen() {
      const [permission, requestPermission] = useCameraPermissions()
    
      if (!permission) return <View />
      
      if (!permission.granted) {
        return (
          <View>
            <Text>Camera permission required</Text>
            <Button onPress={requestPermission} title="Grant Permission" />
          </View>
        )
      }
    
      return (
        <CameraView style={{ flex: 1 }} facing="back">
          <View style={styles.overlay}>
            <Button title="Take Photo" onPress={handleCapture} />
          </View>
        </CameraView>
      )
    }
    

    Notifications

    import * as Notifications from 'expo-notifications'
    import { useEffect, useRef } from 'react'
    
    Notifications.setNotificationHandler({
      handleNotification: async () => ({
        shouldShowAlert: true,
        shouldPlaySound: true,
        shouldSetBadge: true,
      }),
    })
    
    export function useNotifications() {
      const notificationListener = useRef()
      const responseListener = useRef()
    
      useEffect(() => {
        registerForPushNotifications()
    
        notificationListener.current = Notifications.addNotificationReceivedListener(
          (notification) => console.log(notification)
        )
    
        responseListener.current = Notifications.addNotificationResponseReceivedListener(
          (response) => console.log(response)
        )
    
        return () => {
          Notifications.removeNotificationSubscription(notificationListener.current)
          Notifications.removeNotificationSubscription(responseListener.current)
        }
      }, [])
    }
    
    async function registerForPushNotifications() {
      const { status } = await Notifications.requestPermissionsAsync()
      if (status !== 'granted') return
    
      const token = await Notifications.getExpoPushTokenAsync()
      console.log('Push token:', token.data)
    }
    

    Secure Storage

    import * as SecureStore from 'expo-secure-store'
    
    export const secureStorage = {
      async setItem(key: string, value: string) {
        await SecureStore.setItemAsync(key, value)
      },
      
      async getItem(key: string) {
        return await SecureStore.getItemAsync(key)
      },
      
      async removeItem(key: string) {
        await SecureStore.deleteItemAsync(key)
      },
    }
    

    Platform-Specific Code

    import { Platform, StyleSheet } from 'react-native'
    
    // Inline
    <View style={{ paddingTop: Platform.OS === 'ios' ? 20 : 0 }} />
    
    // Platform.select
    const styles = StyleSheet.create({
      container: {
        ...Platform.select({
          ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 } },
          android: { elevation: 4 },
        }),
      },
    })
    
    // File-based (Button.ios.tsx / Button.android.tsx)
    import Button from './Button' // Auto-resolves to platform file
    

    Performance Optimization

    Memoization

    import { memo, useCallback, useMemo } from 'react'
    
    // Memoize components
    const UserCard = memo(function UserCard({ user, onPress }) {
      return (
        <Pressable onPress={() => onPress(user.id)}>
          <Text>{user.name}</Text>
        </Pressable>
      )
    })
    
    // Memoize callbacks
    const handlePress = useCallback((id) => {
      navigation.navigate('User', { id })
    }, [navigation])
    
    // Memoize expensive computations
    const sortedUsers = useMemo(
      () => users.sort((a, b) => a.name.localeCompare(b.name)),
      [users]
    )
    

    Image Optimization

    import { Image } from 'expo-image'
    
    // Use expo-image for better performance
    <Image
      source={{ uri: imageUrl }}
      style={{ width: 200, height: 200 }}
      contentFit="cover"
      placeholder={blurhash}
      transition={200}
    />
    

    Avoid Re-renders

    // Bad: inline function creates new reference
    <FlatList
      renderItem={({ item }) => <Item data={item} />}
    />
    
    // Good: stable callback reference
    const renderItem = useCallback(
      ({ item }) => <Item data={item} />,
      []
    )
    <FlatList renderItem={renderItem} />
    

    Testing

    // __tests__/UserCard.test.tsx
    import { render, fireEvent } from '@testing-library/react-native'
    import { UserCard } from '../components/UserCard'
    
    describe('UserCard', () => {
      it('renders user name', () => {
        const { getByText } = render(
          <UserCard user={{ id: '1', name: 'John' }} />
        )
        expect(getByText('John')).toBeTruthy()
      })
    
      it('calls onPress with user id', () => {
        const onPress = jest.fn()
        const { getByTestId } = render(
          <UserCard user={{ id: '1', name: 'John' }} onPress={onPress} />
        )
        
        fireEvent.press(getByTestId('user-card'))
        expect(onPress).toHaveBeenCalledWith('1')
      })
    })
    

    Common Pitfalls

    1. Don't use flex: 1 without parent flex container
    2. Always handle keyboard on forms
    3. Use keyExtractor in FlatList
    4. Test on both iOS and Android
    5. Handle safe areas properly
    6. Don't forget to add permissions in app.json

    Production Checklist

    • Handle all permission requests gracefully
    • Implement proper error boundaries
    • Add offline support where needed
    • Optimize images and assets
    • Test on physical devices
    • Configure app icons and splash screens
    • Set up EAS Build for production builds
    • Implement deep linking
    • Add analytics and crash reporting
    Recommended Servers
    Vercel Grep
    Vercel Grep
    Databutton
    Databutton
    OpenZeppelin
    OpenZeppelin
    Repository
    speedoa1/everything-opencode
    Files