react-native

React Native and Expo patterns for building performant mobile apps. Covers list performance, animations with Reanimated, navigation, UI patterns, state…

INSTALLATION
npx skills add https://github.com/jezweb/claude-skills --skill react-native
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.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:

<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

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-hapticsHaptics.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

OOM crashes on Android

Resize server-side or use expo-image which handles memory

SVG

SVG support isn't native

react-native-svg + react-native-svg-transformer for SVG imports

Video

Video playback

expo-av or expo-video (newer API)

6. State and Data (MEDIUM)

Pattern

Problem

Fix

AsyncStorage for complex data

JSON parse/stringify on every read

Use MMKV (react-native-mmkv) — 30x faster than AsyncStorage

Global state

Redux/MobX boilerplate for simple state

Zustand — minimal, works great with React Native

Server state

Manual fetch + loading + error + cache

TanStack Query — same as web, works in React Native

Offline first

App unusable without network

TanStack Query persistQueryClient + MMKV, or WatermelonDB for complex offline

Deep state updates

Spread operator hell for nested objects

Immer via Zustand: set(produce(state => { state.user.name = 'new' }))

7. Expo Workflow (MEDIUM)

Pattern

When

How

Development build

Need native modules

npx expo run:ios or eas build --profile development

Expo Go

Quick prototyping, no native modules

npx expo start — scan QR code

EAS Build

CI/CD, app store builds

eas build --platform ios --profile production

EAS Update

Hot fix without app store review

eas update --branch production --message "Fix bug"

Config plugins

Modify native config without ejecting

app.config.ts with expo-build-properties or custom config plugin

Environment variables

Different configs per build

eas.json build profiles + expo-constants

New Project Setup

npx create-expo-app my-app --template tabs

cd my-app

npx expo install expo-image react-native-reanimated react-native-gesture-handler react-native-safe-area-context

8. Testing (LOW-MEDIUM)

Tool

For

Setup

Jest

Unit tests, hook tests

Included with Expo by default

React Native Testing Library

Component tests

@testing-library/react-native

Detox

E2E tests on real devices/simulators

detox — Wix's testing framework

Maestro

E2E with YAML flows

maestro test flow.yaml — simpler than Detox

Common Gotchas

Gotcha

Fix

Metro bundler cache

npx expo start --clear

Pod install issues (iOS)

cd ios &#x26;&#x26; pod install --repo-update

Reanimated not working

Must be first import: import 'react-native-reanimated' in root

Expo SDK upgrade

npx expo install --fix after updating SDK version

Android build fails

Check gradle.properties for memory: org.gradle.jvmargs=-Xmx4g

iOS simulator slow

Use physical device for performance testing — simulator doesn't reflect real perf

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card