home / skills / gentleman-programming / gentleman-skills / react-native

react-native skill

/community/react-native

This skill helps you build robust React Native apps with Expo, React Navigation, and NativeWind by guiding structure, patterns, and platform-specific code.

npx playbooks add skill gentleman-programming/gentleman-skills --skill react-native

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
9.5 KB
---
name: react-native
description: >
  React Native patterns for mobile app development with Expo and bare workflow.
  Trigger: When building mobile apps, working with React Native components, using Expo, React Navigation, or NativeWind.
metadata:
  author: gentleman-programming
  version: "1.0"
---

## When to Use

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 (if using)
│   ├── (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

Always use functional components with proper typing:

```typescript
import { View, Text, Pressable } from 'react-native';
import type { ViewStyle, TextStyle } 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

Use Platform module or file extensions for platform-specific code:

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

// Using Platform.select
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 Navigation Setup

```typescript
// 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

```typescript
// hooks/useUser.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userService } from '@/services/user';
import type { User, UpdateUserInput } from '@/types';

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

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

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

### Example 3: NativeWind Styling

```typescript
// With NativeWind (Tailwind for React Native)
import { View, Text, Pressable } from 'react-native';
import { styled } from 'nativewind';

const StyledPressable = styled(Pressable);
const StyledView = styled(View);
const StyledText = styled(Text);

export function Card({ title, description, onPress }: CardProps) {
  return (
    <StyledPressable
      className="bg-white dark:bg-gray-800 rounded-2xl p-4 shadow-md active:scale-95"
      onPress={onPress}
    >
      <StyledView className="flex-row items-center gap-3">
        <StyledView className="w-12 h-12 bg-blue-100 dark:bg-blue-900 rounded-full items-center justify-center">
          <StyledText className="text-blue-600 dark:text-blue-300 text-xl">
            📱
          </StyledText>
        </StyledView>
        <StyledView className="flex-1">
          <StyledText className="text-lg font-semibold text-gray-900 dark:text-white">
            {title}
          </StyledText>
          <StyledText className="text-sm text-gray-500 dark:text-gray-400">
            {description}
          </StyledText>
        </StyledView>
      </StyledView>
    </StyledPressable>
  );
}
```

### Example 4: Safe Area and Keyboard Handling

```typescript
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 5: Zustand Store with Persistence

```typescript
// stores/authStore.ts
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

```typescript
// ❌ Bad - inline styles are hard to maintain and don't memoize
export function BadComponent() {
  return (
    <View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
      <Text style={{ fontSize: 18, fontWeight: 'bold', color: '#333' }}>
        Title
      </Text>
    </View>
  );
}

// ✅ Good - use StyleSheet or NativeWind
const styles = StyleSheet.create({
  container: { flex: 1, padding: 16, backgroundColor: '#fff' },
  title: { fontSize: 18, fontWeight: 'bold', color: '#333' },
});

export function GoodComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Title</Text>
    </View>
  );
}
```

### Don't: Use TouchableOpacity for Everything

```typescript
// ❌ Bad - TouchableOpacity is legacy
import { TouchableOpacity } from 'react-native';

// ✅ Good - Use Pressable with feedback
import { Pressable } from 'react-native';

<Pressable
  onPress={onPress}
  style={({ pressed }) => [
    styles.button,
    pressed && { opacity: 0.7 }
  ]}
>
  {({ pressed }) => (
    <Text style={pressed ? styles.textPressed : styles.text}>
      Press Me
    </Text>
  )}
</Pressable>
```

### Don't: Forget to Handle Loading and Error States

```typescript
// ❌ Bad - no loading/error handling
export function UserProfile({ userId }: { userId: string }) {
  const { data } = useUser(userId);
  return <Text>{data.name}</Text>; // Will crash if data is undefined
}

// ✅ Good - handle all states
export function UserProfile({ userId }: { userId: string }) {
  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 |
|------|---------|
| Create 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()` from `react-native-safe-area-context` |
| Navigation | `router.push('/screen')` with Expo Router |
| Deep linking | Configure in `app.json` under `expo.scheme` |
| Environment vars | Use `expo-constants` or `react-native-dotenv` |
| Icons | `@expo/vector-icons` (included in Expo) |
| Animations | `react-native-reanimated` for 60fps animations |
| Gestures | `react-native-gesture-handler` |

## Resources

- [Expo Documentation](https://docs.expo.dev/)
- [React Native Documentation](https://reactnative.dev/docs/getting-started)
- [Expo Router](https://expo.github.io/router/docs/)
- [NativeWind](https://www.nativewind.dev/)
- [React Navigation](https://reactnavigation.org/docs/getting-started)

Overview

This skill provides practical React Native patterns and conventions for building mobile apps with Expo or a bare React Native workflow. It focuses on project structure, TypeScript functional components, navigation with Expo Router/React Navigation, NativeWind styling, platform-specific handling, and state persistence. Use it as a concise reference to standardize app architecture and avoid common pitfalls.

How this skill works

The skill inspects typical mobile app workflows and recommends patterns: a clear src/ folder layout, typed functional components, platform-specific file strategies, and common libraries like React Query, Zustand, and NativeWind. It includes examples for Expo Router layout, hooks with React Query, safe area/keyboard handling, and persisted Zustand stores to speed implementation. Anti-patterns highlight what to avoid and replacements to prefer.

When to use it

  • Starting a new Expo or bare React Native project and defining folder layout
  • Implementing navigation with Expo Router or React Navigation
  • Styling components with NativeWind (Tailwind for React Native)
  • Handling platform-specific UI/behavior for iOS and Android
  • Managing remote data with React Query and local state with Zustand
  • Adding persistent auth or storage with AsyncStorage

Best practices

  • Organize code under src/ with clear domains: app, components, hooks, services, stores, utils, types
  • Prefer typed functional components in TypeScript and memoize where appropriate
  • Use Pressable for touchable UI and provide visual feedback via style callbacks
  • Handle loading, error, and empty states for all async data
  • Use Platform.select or platform-specific file extensions (.ios/.android) for divergent implementations
  • Prefer StyleSheet or NativeWind classes over inline styles for performance and maintainability

Example use cases

  • Scaffold an Expo app: tabs + modal flows with an app/_layout.tsx using Expo Router
  • Create a typed Button component with Pressable and pressed-state styling
  • Build a useUser hook with React Query and a useUpdateUser mutation that invalidates queries
  • Style reusable UI cards with NativeWind for dark/light theme support
  • Wrap screens with SafeAreaView and KeyboardAvoidingView for correct insets and keyboard behavior
  • Persist auth state with Zustand and AsyncStorage using zustand/middleware

FAQ

Should I use Expo or bare workflow?

Choose Expo for fast iteration, built-in tooling, and managed native modules. Use bare workflow when you need custom native code or modules not supported by Expo.

When to use NativeWind vs StyleSheet?

Use NativeWind for rapid utility-first styling and theme consistency. Use StyleSheet for highly dynamic or performance-critical styles and when you need precise platform optimizations.