| name | react-native |
| description | React Native and Expo rules, patterns, and checklists. Load when react-native is detected. |
Detection Signals
Load this skill when any of the following are found:
| Signal | File / Dependency |
|---|
react-native in package.json dependencies | package.json |
| Metro bundler config | metro.config.js / metro.config.ts |
| React Native CLI project | android/ + ios/ directories together |
| Expo project | app.json with "expo" key, eas.json, or expo in package.json |
| Expo Modules | expo-modules-core in package.json |
Project Structure
src/
├── app/ # Root navigation and app entry
├── components/ # Reusable, platform-agnostic UI components
├── screens/ # Screen-level components (one per route)
├── hooks/ # Custom hooks (no UI, no side effects tied to render)
├── store/ # Global state (Redux, Zustand, Jotai)
├── services/ # API clients, native module wrappers
├── utils/ # Pure functions, formatters, constants
├── types/ # Shared TypeScript types and interfaces
└── assets/ # Images, fonts, icons
- One screen = one file; never put multiple screens in one file
- Platform-specific files use
.ios.tsx / .android.tsx suffixes — only when unavoidable
- Shared business logic must live in hooks or services, never inside screen components
Expo: Managed vs Bare Workflow
→ Load skills/mobile/react-native/references/expo-vs-bare.md when deciding between Managed and Bare workflows, configuring EAS Build, or handling OTA updates.
Navigation
→ Load skills/mobile/react-native/references/navigation.md when working with React Navigation, deep links, or back behavior.
State Management
→ Load skills/mobile/react-native/references/state-management.md when choosing or implementing state management (Zustand, Redux Toolkit, Jotai, TanStack Query).
Performance
Lists
- Always use
FlatList or SectionList for dynamic lists — never ScrollView + .map() for more than ~10 items
- Prefer FlashList (
@shopify/flash-list) over FlatList for large lists (> 100 items) — it is significantly faster
- Set
keyExtractor to a stable, unique string — never use array index
- Use
getItemLayout when item height is fixed to skip measurement overhead
removeClippedSubviews={true} for long lists on Android
Renders
React.memo on components that receive stable props and re-render often
useCallback on functions passed as props to memoized children
useMemo only for genuinely expensive computations — not as a default
- Avoid anonymous functions and inline objects in JSX props (create new references each render)
Images
- Use
expo-image or react-native-fast-image instead of the built-in Image — both support caching and progressive loading
- Always specify
width and height — never let images cause layout shift
- Use WebP format for all non-transparent images
JavaScript Engine
- Hermes is enabled by default in React Native 0.70+ and all Expo SDK 47+ projects — do not disable it
- Profile with Flipper (bare) or React DevTools Profiler (both workflows) before optimizing
Native Integrations
Permissions (Runtime)
Always request permissions at the moment they are needed, not at app launch:
import * as Location from 'expo-location';
import { request, PERMISSIONS } from 'react-native-permissions';
const requestLocation = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
}
};
- Check permission status before requesting — avoid re-prompting after a denial
- Always handle the
denied and blocked states gracefully
- On iOS: populate all
NSUsageDescription keys in app.json → ios.infoPlist before submitting
Push Notifications
- Use
expo-notifications (Managed) or @react-native-firebase/messaging (Bare/Firebase)
- Always request permission before registering for push tokens
- Store the device token server-side tied to the user, not the device
- Handle foreground, background, and quit-state notifications explicitly
Expo Modules API (custom native modules)
When a native module is not available in the Expo SDK:
- Create an Expo Module:
npx create-expo-module my-module
- Implement in Swift (iOS) and Kotlin (Android) within the module
- Export via
ExpoModulesCore — avoids the old bridge entirely
- For Bare workflow: native modules can still use the traditional bridge, but prefer the new architecture (TurboModules) for new code
Security
- Never hardcode API keys or secrets in JS code — they end up in the bundle; use environment variables via
expo-constants or a secrets manager
- Secure storage for tokens:
expo-secure-store (Managed) or react-native-keychain (Bare) — both use iOS Keychain and Android Keystore
- Certificate pinning: implement for apps handling financial or health data
- Jailbreak/root detection: use
expo-device (isRooted) or react-native-jail-monkey for sensitive apps
- Network: enforce HTTPS; configure App Transport Security (iOS) and Network Security Config (Android) to disallow cleartext
Testing
| Layer | Tool | Purpose |
|---|
| Unit | Jest + @testing-library/react-native | Component rendering and hooks |
| Integration | Detox | Full device E2E (real device or simulator) |
| Manual | Physical device on each target OS version | Permissions, gestures, platform quirks |
- Test on a real device before any release — simulators do not replicate memory pressure, battery, or actual GPU behavior
- Detox tests must run in CI against the
preview build profile
- Snapshot tests: use sparingly — they break on any style change and add noise
App Store & Play Store — Publication Checklist
Both Platforms
iOS (App Store)
Android (Play Store)