react-native

Installation
SKILL.md

When to Use

Triggers: When building mobile apps, working with React Native components, using Expo, React Navigation, or NativeWind.

Load this skill when:

  • Building mobile applications with React Native
  • Working with Expo managed or bare workflow
  • Implementing navigation with React Navigation
  • Styling with NativeWind (Tailwind for RN)
  • Handling platform-specific code (iOS/Android)
  • Managing native modules and linking

Critical Patterns

Pattern 1: Project Structure

src/
├── app/                    # Expo Router screens
│   ├── (tabs)/            # Tab navigator group
│   ├── (auth)/            # Auth flow group
│   └── _layout.tsx        # Root layout
├── components/
│   ├── ui/                # Reusable UI components
│   └── features/          # Feature-specific components
├── hooks/                 # Custom hooks
├── services/              # API and external services
├── stores/                # State management (Zustand)
├── utils/                 # Utility functions
├── constants/             # App constants, themes
└── types/                 # TypeScript types

Pattern 2: Functional Components with TypeScript

import { View, Text, Pressable } from 'react-native';

interface ButtonProps {
  title: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

export function Button({
  title,
  onPress,
  variant = 'primary',
  disabled = false
}: ButtonProps) {
  return (
    <Pressable
      onPress={onPress}
      disabled={disabled}
      style={({ pressed }) => [
        styles.button,
        variant === 'secondary' && styles.buttonSecondary,
        pressed && styles.buttonPressed,
        disabled && styles.buttonDisabled,
      ]}
    >
      <Text style={styles.buttonText}>{title}</Text>
    </Pressable>
  );
}

Pattern 3: Platform-Specific Code

import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.select({ ios: 44, android: 0 }),
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.1,
        shadowRadius: 4,
      },
      android: { elevation: 4 },
    }),
  },
});

// Or use file extensions:
// Component.ios.tsx
// Component.android.tsx

Code Examples

Example 1: Expo Router Layout

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

export default function RootLayout() {
  return (
    <>
      <StatusBar style="auto" />
      <Stack screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen
          name="modal"
          options={{ presentation: 'modal', animation: 'slide_from_bottom' }}
        />
      </Stack>
    </>
  );
}

Example 2: Custom Hook with React Query

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userService } from '@/services/user';

export function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => userService.getById(userId),
    staleTime: 5 * 60 * 1000,
  });
}

export function useUpdateUser() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (data: UpdateUserInput) => userService.update(data),
    onSuccess: (_, variables) => {
      queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
    },
  });
}

Example 3: Safe Area + Keyboard Handling

import { KeyboardAvoidingView, Platform } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

export function ScreenWrapper({ children }: { children: React.ReactNode }) {
  return (
    <SafeAreaView style={{ flex: 1 }} edges={['top', 'left', 'right']}>
      <KeyboardAvoidingView
        style={{ flex: 1 }}
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
      >
        {children}
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
}

Example 4: Zustand Store with AsyncStorage

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

interface AuthState {
  token: string | null;
  user: User | null;
  isAuthenticated: boolean;
  login: (token: string, user: User) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      user: null,
      isAuthenticated: false,
      login: (token, user) => set({ token, user, isAuthenticated: true }),
      logout: () => set({ token: null, user: null, isAuthenticated: false }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

Anti-Patterns

Don't: Inline Styles Everywhere

// ❌ Bad
<View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>

// ✅ Good - StyleSheet or NativeWind
const styles = StyleSheet.create({
  container: { flex: 1, padding: 16, backgroundColor: '#fff' },
});

Don't: Use TouchableOpacity (legacy)

// ❌ Bad
import { TouchableOpacity } from 'react-native';

// ✅ Good
import { Pressable } from 'react-native';
<Pressable
  onPress={onPress}
  style={({ pressed }) => [styles.button, pressed && { opacity: 0.7 }]}
>

Don't: Forget Loading/Error States

// ❌ Bad
const { data } = useUser(userId);
return <Text>{data.name}</Text>; // crash if undefined

// ✅ Good
const { data, isLoading, error } = useUser(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;
return <Text>{data.name}</Text>;

Quick Reference

Task Pattern
New Expo project npx create-expo-app@latest --template tabs
Add NativeWind npx expo install nativewind tailwindcss
Platform check Platform.OS === 'ios'
Safe insets useSafeAreaInsets()
Navigation router.push('/screen') (Expo Router)
Icons @expo/vector-icons
Animations react-native-reanimated
Gestures react-native-gesture-handler

Rules

  • Use Expo managed workflow by default for new projects; bare workflow is appropriate only when a native module unavailable in Expo is strictly required
  • Navigation must be implemented with React Navigation; never use ad-hoc conditional rendering or useState to simulate navigation stacks
  • NativeWind (Tailwind for React Native) styles must use className prop — never mix NativeWind with inline style objects on the same component
  • Platform-specific code must be isolated with Platform.select() or .ios.tsx/.android.tsx file extensions; never use Platform.OS checks inline in JSX
  • All async operations (permissions, storage, network) must handle loading and error states explicitly — unhandled promise rejections crash the app silently on some platforms
Weekly Installs
19
First Seen
Today