react-native-expo

React Native Development Expert

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 "react-native-expo" with this command: npx skills add 0xkynz/codekit/0xkynz-codekit-react-native-expo

React Native Development Expert

Expert in React Native development with Expo, TypeScript, and modern mobile tooling. Specialized in building performant cross-platform mobile applications with best practices.

When to Use

  • React Native projects (Expo or bare workflow)

  • Cross-platform mobile applications (iOS & Android)

  • Mobile apps with native functionality

  • Projects requiring native device features

For web-only React projects, use react agent instead.

Technology Stack

Core

  • React Native: Cross-platform mobile framework

  • Expo SDK 52+: Managed workflow and native APIs

  • TypeScript: Strict typing and best practices

  • Expo Router: File-based navigation

UI/Styling

  • NativeWind: Tailwind CSS for React Native

  • React Native Reanimated: Smooth animations

  • React Native Gesture Handler: Touch interactions

  • Expo Vector Icons: Icon library

Navigation

  • Expo Router: File-based routing (recommended)

  • React Navigation: Stack, Tab, Drawer navigators

Data & State

  • TanStack Query: Server state management

  • Zustand: Client state management

  • React Hook Form + Zod: Form handling

  • MMKV: Fast key-value storage

  • Expo SecureStore: Secure data storage

Native APIs

  • Expo Camera: Camera access

  • Expo Notifications: Push notifications

  • Expo Location: Geolocation

  • Expo Image Picker: Media selection

  • Expo FileSystem: File operations

Project Structure

/my-react-native-app ├── /app/ # Expo Router screens │ ├── (tabs)/ # Tab navigator group │ │ ├── index.tsx # Home tab │ │ ├── profile.tsx # Profile tab │ │ └── _layout.tsx # Tab layout │ ├── (auth)/ # Auth screens group │ │ ├── login.tsx │ │ ├── register.tsx │ │ └── _layout.tsx │ ├── [id].tsx # Dynamic route │ ├── _layout.tsx # Root layout │ └── +not-found.tsx # 404 screen ├── /src/ │ ├── /components/ # Reusable components │ │ ├── /ui/ # Base UI (Button, Input, Card) │ │ ├── /forms/ # Form components │ │ └── /lists/ # List components │ ├── /features/ # Feature modules │ │ ├── /auth/ │ │ │ ├── /components/ │ │ │ ├── /hooks/ │ │ │ ├── /services/ │ │ │ └── index.ts │ │ └── /settings/ │ ├── /hooks/ # Custom hooks │ ├── /services/ # API services │ ├── /store/ # State management │ ├── /types/ # TypeScript types │ ├── /utils/ # Utilities │ ├── /constants/ # App constants │ └── /theme/ # Theme configuration ├── /assets/ # Images, fonts, etc. ├── app.json # Expo config ├── eas.json # EAS Build config ├── tailwind.config.js # NativeWind config ├── tsconfig.json └── package.json

Code Standards

Component Pattern

import { View, Text, Pressable } from "react-native"; import { forwardRef } from "react"; import { cn } from "@/utils/cn";

interface ButtonProps { variant?: "default" | "outline" | "ghost"; size?: "sm" | "md" | "lg"; onPress?: () => void; disabled?: boolean; className?: string; children: React.ReactNode; }

const Button = forwardRef<View, ButtonProps>( ({ variant = "default", size = "md", className, children, ...props }, ref) => { return ( <Pressable ref={ref} className={cn( "items-center justify-center rounded-lg", variants[variant], sizes[size], props.disabled && "opacity-50", className )} {...props} > <Text className={cn("font-medium", textVariants[variant])}> {children} </Text> </Pressable> ); } ); Button.displayName = "Button";

export { Button };

Custom Hook Pattern

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

export function useUsers() { return useQuery({ queryKey: ["users"], queryFn: () => userService.getAll(), }); }

export function useCreateUser() { const queryClient = useQueryClient();

return useMutation({ mutationFn: userService.create, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["users"] }); }, }); }

Form Pattern (React Hook Form + Zod)

import { View, TextInput, Text, Pressable } from "react-native"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod";

const schema = z.object({ email: z.string().email(), password: z.string().min(8), });

type FormData = z.infer<typeof schema>;

export function LoginForm() { const { control, handleSubmit, formState: { errors } } = useForm<FormData>({ resolver: zodResolver(schema), });

const onSubmit = (data: FormData) => { // Handle submission };

return ( <View className="gap-4"> <Controller control={control} name="email" render={({ field: { onChange, onBlur, value } }) => ( <View> <TextInput className="border border-gray-300 rounded-lg px-4 py-3" placeholder="Email" onBlur={onBlur} onChangeText={onChange} value={value} keyboardType="email-address" autoCapitalize="none" /> {errors.email && ( <Text className="text-red-500 text-sm mt-1"> {errors.email.message} </Text> )} </View> )} /> <Controller control={control} name="password" render={({ field: { onChange, onBlur, value } }) => ( <View> <TextInput className="border border-gray-300 rounded-lg px-4 py-3" placeholder="Password" onBlur={onBlur} onChangeText={onChange} value={value} secureTextEntry /> {errors.password && ( <Text className="text-red-500 text-sm mt-1"> {errors.password.message} </Text> )} </View> )} /> <Pressable className="bg-blue-500 rounded-lg py-3 items-center" onPress={handleSubmit(onSubmit)} > <Text className="text-white font-semibold">Login</Text> </Pressable> </View> ); }

Expo Router Layout

// app/_layout.tsx import { Stack } from "expo-router"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import "../global.css";

const queryClient = new QueryClient();

export default function RootLayout() { return ( <GestureHandlerRootView style={{ flex: 1 }}> <QueryClientProvider client={queryClient}> <Stack> <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack.Screen name="(auth)" options={{ headerShown: false }} /> </Stack> </QueryClientProvider> </GestureHandlerRootView> ); }

Tab Navigator Layout

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

export default function TabLayout() { return ( <Tabs screenOptions={{ tabBarActiveTintColor: "#3b82f6", tabBarInactiveTintColor: "#9ca3af", }} > <Tabs.Screen name="index" options={{ title: "Home", tabBarIcon: ({ color, size }) => ( <Ionicons name="home" size={size} color={color} /> ), }} /> <Tabs.Screen name="profile" options={{ title: "Profile", tabBarIcon: ({ color, size }) => ( <Ionicons name="person" size={size} color={color} /> ), }} /> </Tabs> ); }

List with FlashList

import { FlashList } from "@shopify/flash-list"; import { View, Text, Pressable } from "react-native";

interface User { id: string; name: string; email: string; }

interface UsersListProps { users: User[]; onUserPress: (user: User) => void; }

export function UsersList({ users, onUserPress }: UsersListProps) { const renderItem = ({ item }: { item: User }) => ( <Pressable className="bg-white p-4 border-b border-gray-100" onPress={() => onUserPress(item)} > <Text className="font-semibold text-gray-900">{item.name}</Text> <Text className="text-gray-500 text-sm">{item.email}</Text> </Pressable> );

return ( <FlashList data={users} renderItem={renderItem} estimatedItemSize={72} keyExtractor={(item) => item.id} /> ); }

Animation Pattern (Reanimated)

import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, } from "react-native-reanimated"; import { Pressable } from "react-native";

export function AnimatedButton({ children, onPress }) { const scale = useSharedValue(1);

const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], }));

const handlePressIn = () => { scale.value = withSpring(0.95); };

const handlePressOut = () => { scale.value = withSpring(1); };

return ( <Pressable onPressIn={handlePressIn} onPressOut={handlePressOut} onPress={onPress} > <Animated.View style={animatedStyle}>{children}</Animated.View> </Pressable> ); }

App Configuration

// app.json { "expo": { "name": "My App", "slug": "my-app", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "scheme": "myapp", "userInterfaceStyle": "automatic", "splash": { "image": "./assets/splash.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, "bundleIdentifier": "com.company.myapp" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" }, "package": "com.company.myapp" }, "plugins": [ "expo-router", "expo-secure-store", [ "expo-camera", { "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera" } ] ] } }

Best Practices

Component Organization

  • Use feature-based folder structure

  • Colocate related code (components, hooks, types)

  • Use barrel exports (index.ts)

  • Keep components small and focused

State Management

  • Server state: TanStack Query

  • Client state: Zustand

  • Form state: React Hook Form

  • Navigation state: Expo Router

  • Persistent state: MMKV or SecureStore

Performance

  • Use FlashList instead of FlatList for long lists

  • Avoid inline styles and functions in render

  • Use React.memo for expensive components

  • Implement skeleton loaders for async content

  • Use Reanimated for smooth animations

Styling

  • Use NativeWind for Tailwind-like styling

  • Support dark mode via useColorScheme

  • Use consistent spacing and typography

  • Handle safe areas with SafeAreaView

TypeScript

  • Define interfaces for all props

  • Use strict mode

  • Type navigation params properly

  • Use Zod for runtime validation

Platform Handling

  • Use Platform.select() for platform-specific code

  • Create .ios.tsx and .android.tsx files when needed

  • Test on both platforms regularly

  • Handle keyboard avoidance properly

Accessibility

  • Add accessibilityLabel to interactive elements

  • Use accessibilityRole appropriately

  • Ensure adequate touch target sizes (44x44 minimum)

  • Support dynamic text sizes

Error Handling

  • Implement error boundaries

  • Handle network errors gracefully

  • Show meaningful error messages

  • Add retry mechanisms for failed requests

Quick Setup Commands

Create new Expo project

npx create-expo-app@latest my-app --template tabs cd my-app

Install core dependencies

npx expo install @tanstack/react-query npm install zustand npm install react-hook-form @hookform/resolvers zod

Install UI/Animation

npx expo install react-native-reanimated react-native-gesture-handler npm install nativewind tailwindcss

Install FlashList for performant lists

npx expo install @shopify/flash-list

Install storage

npx expo install react-native-mmkv expo-secure-store

Initialize NativeWind

npx tailwindcss init

Start development

npx expo start

EAS Build Configuration

// eas.json { "cli": { "version": ">= 5.0.0" }, "build": { "development": { "developmentClient": true, "distribution": "internal" }, "preview": { "distribution": "internal" }, "production": {} }, "submit": { "production": {} } }

Build for development

eas build --profile development --platform ios

Build for production

eas build --profile production --platform all

Submit to stores

eas submit --platform ios eas submit --platform android

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.

Coding

uiux-design-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

figma-make-website-builder

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

data-visualization

No summary provided by upstream source.

Repository SourceNeeds Review