react-native
This React Native skill provides performance and architecture patterns for building Expo and React Native apps, covering list virtualization, animations with Reanimated, navigation, and state management. Use it when building or reviewing mobile code, optimizing list and scroll performance, implementing animations, or setting up new Expo projects.
git clone --depth 1 https://github.com/jezweb/claude-skills /tmp/react-native && cp -r /tmp/react-native/plugins/frontend/skills/react-native ~/.claude/skills/react-nativeSKILL.md
# React Native Patterns
Performance and architecture patterns for React Native + Expo apps. Rules ranked by impact — fix CRITICAL before touching MEDIUM.
This is a starting point. The skill will grow as you build more mobile apps.
## When to Apply
- Building new React Native or Expo apps
- Optimising list and scroll performance
- Implementing animations
- Reviewing mobile code for performance issues
- Setting up a new Expo project
## 1. List Performance (CRITICAL)
Lists are the #1 performance issue in React Native. A janky scroll kills the entire app experience.
| Pattern | Problem | Fix |
|---------|---------|-----|
| **ScrollView for data** | `<ScrollView>` renders all items at once | Use `<FlatList>` or `<FlashList>` — virtualised, only renders visible items |
| **Missing keyExtractor** | FlatList without `keyExtractor` → unnecessary re-renders | `keyExtractor={(item) => item.id}` — stable unique key per item |
| **Complex renderItem** | Expensive component in renderItem re-renders on every scroll | Wrap in `React.memo`, extract to separate component |
| **Inline functions in renderItem** | `renderItem={({ item }) => <Row onPress={() => nav(item.id)} />}` | Extract handler: `const handlePress = useCallback(...)` |
| **No getItemLayout** | FlatList measures every item on scroll (expensive) | Provide `getItemLayout` for fixed-height items: `(data, index) => ({ length: 80, offset: 80 * index, index })` |
| **FlashList** | FlatList is good, FlashList is better for large lists | `@shopify/flash-list` — drop-in replacement, recycling architecture |
| **Large images in lists** | Full-res images decoded on main thread | Use `expo-image` with placeholder + transition, specify dimensions |
### FlatList Checklist
Every FlatList should have:
```tsx
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={renderItem} // Memoised component
getItemLayout={getItemLayout} // If items are fixed height
initialNumToRender={10} // Don't render 100 items on mount
maxToRenderPerBatch={10} // Batch size for off-screen rendering
windowSize={5} // How many screens to keep in memory
removeClippedSubviews={true} // Unmount off-screen items (Android)
/>
```
## 2. Animations (HIGH)
Native animations run on the UI thread. JS animations block the JS thread and cause jank.
| Pattern | Problem | Fix |
|---------|---------|-----|
| **Animated API for complex animations** | `Animated` runs on JS thread, blocks interactions | Use `react-native-reanimated` — runs on UI thread |
| **Layout animation** | Item appears/disappears with no transition | `LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)` |
| **Shared element transitions** | Navigate between screens, element teleports | `react-native-reanimated` shared transitions or `expo-router` shared elements |
| **Gesture + animation** | Drag/swipe feels laggy | `react-native-gesture-handler` + `reanimated` worklets — all on UI thread |
| **Measuring layout** | `onLayout` fires too late, causes flash | Use `useAnimatedStyle` with shared values for instant response |
### Reanimated Basics
```tsx
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
function AnimatedBox() {
const offset = useSharedValue(0);
const style = useAnimatedStyle(() => ({
transform: [{ translateX: withSpring(offset.value) }],
}));
return (
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, style]} />
</GestureDetector>
);
}
```
## 3. Navigation (HIGH)
| Pattern | Problem | Fix |
|---------|---------|-----|
| **Expo Router** | File-based routing (like Next.js) for React Native | `app/` directory with `_layout.tsx` files. Preferred for new Expo projects. |
| **Heavy screens on stack** | Every screen stays mounted in the stack | Use `unmountOnBlur: true` for screens that don't need to persist |
| **Deep linking** | App doesn't respond to URLs | Expo Router handles this automatically. For bare RN: `Linking` API config |
| **Tab badge updates** | Badge count doesn't update when tab is focused | Use `useIsFocused()` or refetch on focus: `useFocusEffect(useCallback(...))` |
| **Navigation state persistence** | App loses position on background/kill | `onStateChange` + `initialState` with AsyncStorage |
### Expo Router Structure
```
app/
├── _layout.tsx # Root layout (tab navigator)
├── index.tsx # Home tab
├── (tabs)/
│ ├── _layout.tsx # Tab bar config
│ ├── home.tsx
│ ├── search.tsx
│ └── profile.tsx
├── [id].tsx # Dynamic route
└── modal.tsx # Modal route
```
## 4. UI Patterns (HIGH)
| Pattern | Problem | Fix |
|---------|---------|-----|
| **Safe area** | Content under notch or home indicator | `<SafeAreaView>` or `useSafeAreaInsets()` from `react-native-safe-area-context` |
| **Keyboard avoidance** | Form fields hidden behind keyboard | `<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>` |
| **Platform-specific code** | iOS and Android need different behaviour | `Platform.select({ ios: ..., android: ... })` or `.ios.tsx` / `.android.tsx` files |
| **Status bar** | Status bar overlaps content or wrong colour | `<StatusBar style="auto" />` from `expo-status-bar` in root layout |
| **Touch targets** | Buttons too small to tap | Minimum 44x44pt. Use `hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}` |
| **Haptic feedback** | Taps feel dead | `expo-haptics` — `Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)` on important actions |
## 5. Images and Media (MEDIUM)
| Pattern | Problem | Fix |
|---------|---------|-----|
| **Image component** | `<Image>` from react-native is basic | Use `expo-image` — caching, placeholder, transition, blurhash |
| **Remote images without dimensions** | Layout shift when image loads | Always specify `width` and `height`, or use `aspectRatio` |
| **Large images** | OOMHit the Cloudflare REST API directly for operations that wrangler and MCP can't handle well. Bulk DNS, custom hostnames, email routing, cache purge, WAF rules, redirect rules, zone settings, Worker routes, D1 cross-database queries, R2 bulk operations, KV bulk read/write, Vectorize queries, Queues, and fleet-wide resource audits. Produces curl commands or scripts. Triggers: 'cloudflare api', 'bulk dns', 'custom hostname', 'email routing', 'cache purge', 'waf rule', 'd1 query', 'r2 bucket', 'kv bulk', 'vectorize query', 'audit resources', 'fleet operation'.
Scaffold and deploy Cloudflare Workers with Hono routing, Vite plugin, and Static Assets. Describe project, scaffold structure, configure bindings, deploy. Use whenever the user wants to create a Worker project, set up Hono on Cloudflare, configure D1 / R2 / KV / Queues bindings, or troubleshoot Worker export syntax, API route conflicts, HMR issues, or deployment failures.
Generate Drizzle ORM schemas for Cloudflare D1 databases with correct D1-specific patterns. Produces schema files, migration commands, type exports, and DATABASE_SCHEMA.md documentation. Handles D1 quirks: foreign keys always enforced, no native BOOLEAN/DATETIME types, 100 bound parameter limit, JSON stored as TEXT. Use when creating a new database, adding tables, or scaffolding a D1 data layer.
Cloudflare D1 migration workflow: generate with Drizzle, inspect SQL for gotchas, apply to local and remote, fix stuck migrations, handle partial failures. Use when running migrations, fixing migration errors, or setting up D1 schemas.
Generate database seed scripts with realistic sample data. Reads Drizzle schemas or SQL migrations, respects foreign key ordering, produces idempotent TypeScript or SQL seed files. Handles D1 batch limits, unique constraints, and domain-appropriate data. Use when populating dev/demo/test databases. Triggers: 'seed database', 'seed data', 'sample data', 'populate database', 'db seed', 'test data', 'demo data', 'generate fixtures'.
Scaffold Hono API routes for Cloudflare Workers. Produces route files, middleware, typed bindings, Zod validation, error handling, and API_ENDPOINTS.md documentation. Use after a project is set up with cloudflare-worker-builder or vite-flare-starter, when you need to add API routes, create endpoints, or generate API documentation.
Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per project.
Scaffold a full-stack Cloudflare app from the vite-flare-starter template — React 19 + Hono + D1+Drizzle + better-auth + Tailwind v4+shadcn/ui + TanStack Query + R2 + Workers AI. Run setup.sh to clone, configure, and deploy. Use whenever the user wants a batteries-included Cloudflare full-stack app, vite-flare-starter scaffold, or a React + Cloudflare app with auth + database + Workers AI ready to go.