Claude-skills react-native

React Native and Expo patterns for building performant mobile apps. Covers list performance, animations with Reanimated, navigation, UI patterns, state management, platform-specific code, and Expo workflows. Use when building or reviewing React Native code. Triggers: 'react native', 'expo', 'mobile app', 'react native performance', 'flatlist', 'reanimated', 'expo router', 'mobile development', 'ios app', 'android app'.

install
source · Clone the upstream repo
git clone https://github.com/jezweb/claude-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jezweb/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/frontend/skills/react-native" ~/.claude/skills/jezweb-claude-skills-react-native && rm -rf "$T"
manifest: plugins/frontend/skills/react-native/SKILL.md
source content

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.

PatternProblemFix
ScrollView for data
<ScrollView>
renders all items at once
Use
<FlatList>
or
<FlashList>
— virtualised, only renders visible items
Missing keyExtractorFlatList without
keyExtractor
→ unnecessary re-renders
keyExtractor={(item) => item.id}
— stable unique key per item
Complex renderItemExpensive component in renderItem re-renders on every scrollWrap in
React.memo
, extract to separate component
Inline functions in renderItem
renderItem={({ item }) => <Row onPress={() => nav(item.id)} />}
Extract handler:
const handlePress = useCallback(...)
No getItemLayoutFlatList measures every item on scroll (expensive)Provide
getItemLayout
for fixed-height items:
(data, index) => ({ length: 80, offset: 80 * index, index })
FlashListFlatList is good, FlashList is better for large lists
@shopify/flash-list
— drop-in replacement, recycling architecture
Large images in listsFull-res images decoded on main threadUse
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.

PatternProblemFix
Animated API for complex animations
Animated
runs on JS thread, blocks interactions
Use
react-native-reanimated
— runs on UI thread
Layout animationItem appears/disappears with no transition
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
Shared element transitionsNavigate between screens, element teleports
react-native-reanimated
shared transitions or
expo-router
shared elements
Gesture + animationDrag/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)

PatternProblemFix
Expo RouterFile-based routing (like Next.js) for React Native
app/
directory with
_layout.tsx
files. Preferred for new Expo projects.
Heavy screens on stackEvery screen stays mounted in the stackUse
unmountOnBlur: true
for screens that don't need to persist
Deep linkingApp doesn't respond to URLsExpo Router handles this automatically. For bare RN:
Linking
API config
Tab badge updatesBadge count doesn't update when tab is focusedUse
useIsFocused()
or refetch on focus:
useFocusEffect(useCallback(...))
Navigation state persistenceApp 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)

PatternProblemFix
Safe areaContent under notch or home indicator
<SafeAreaView>
or
useSafeAreaInsets()
from
react-native-safe-area-context
Keyboard avoidanceForm fields hidden behind keyboard
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
Platform-specific codeiOS and Android need different behaviour
Platform.select({ ios: ..., android: ... })
or
.ios.tsx
/
.android.tsx
files
Status barStatus bar overlaps content or wrong colour
<StatusBar style="auto" />
from
expo-status-bar
in root layout
Touch targetsButtons too small to tapMinimum 44x44pt. Use
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
Haptic feedbackTaps feel dead
expo-haptics
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
on important actions

5. Images and Media (MEDIUM)

PatternProblemFix
Image component
<Image>
from react-native is basic
Use
expo-image
— caching, placeholder, transition, blurhash
Remote images without dimensionsLayout shift when image loadsAlways specify
width
and
height
, or use
aspectRatio
Large imagesOOM crashes on AndroidResize server-side or use
expo-image
which handles memory
SVGSVG support isn't native
react-native-svg
+
react-native-svg-transformer
for SVG imports
VideoVideo playback
expo-av
or
expo-video
(newer API)

6. State and Data (MEDIUM)

PatternProblemFix
AsyncStorage for complex dataJSON parse/stringify on every readUse MMKV (
react-native-mmkv
) — 30x faster than AsyncStorage
Global stateRedux/MobX boilerplate for simple stateZustand — minimal, works great with React Native
Server stateManual fetch + loading + error + cacheTanStack Query — same as web, works in React Native
Offline firstApp unusable without networkTanStack Query
persistQueryClient
+ MMKV, or WatermelonDB for complex offline
Deep state updatesSpread operator hell for nested objectsImmer via Zustand:
set(produce(state => { state.user.name = 'new' }))

7. Expo Workflow (MEDIUM)

PatternWhenHow
Development buildNeed native modules
npx expo run:ios
or
eas build --profile development
Expo GoQuick prototyping, no native modules
npx expo start
— scan QR code
EAS BuildCI/CD, app store builds
eas build --platform ios --profile production
EAS UpdateHot fix without app store review
eas update --branch production --message "Fix bug"
Config pluginsModify native config without ejecting
app.config.ts
with
expo-build-properties
or custom config plugin
Environment variablesDifferent 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)

ToolForSetup
JestUnit tests, hook testsIncluded with Expo by default
React Native Testing LibraryComponent tests
@testing-library/react-native
DetoxE2E tests on real devices/simulators
detox
— Wix's testing framework
MaestroE2E with YAML flows
maestro test flow.yaml
— simpler than Detox

Common Gotchas

GotchaFix
Metro bundler cache
npx expo start --clear
Pod install issues (iOS)
cd ios && pod install --repo-update
Reanimated not workingMust be first import:
import 'react-native-reanimated'
in root
Expo SDK upgrade
npx expo install --fix
after updating SDK version
Android build failsCheck
gradle.properties
for memory:
org.gradle.jvmargs=-Xmx4g
iOS simulator slowUse physical device for performance testing — simulator doesn't reflect real perf