React Native Skill
When to use
- Creating or modifying React Native screens, components, or navigation stacks
- Choosing between bare React Native and Expo managed/bare workflow
- Integrating native modules (camera, biometrics, BLE, push notifications)
- Diagnosing bridge overhead, JS thread jank, or memory pressure
- Configuring Metro, Babel, or Expo app.json / app.config.ts
- Preparing an app for App Store and Google Play submission
Workflow
- Confirm workflow: Expo managed workflow (no native code edited), Expo bare workflow (has
ios/andandroid/folders), or plain React Native CLI. Each has different upgrade and native module strategies. - Project bootstrap:
- Expo:
npx create-expo-app --template— pick a TypeScript template - Bare RN:
npx @react-native-community/cli init ProjectName --template react-native-template-typescript - Never bootstrap with JavaScript-only template for a new project; TypeScript from day one
- Expo:
- Navigation: use React Navigation v6+ (
@react-navigation/native). Define a typed root navigator insrc/navigation/RootNavigator.tsx; exportRootStackParamListfor end-to-end type safety onnavigation.navigate(). - State management:
- Server/async state:
@tanstack/react-querywith a typedqueryClient - Local UI state:
useState/useReducer - Global client state: Zustand (lightweight) or Redux Toolkit (when team size or audit requirements demand it)
- Server/async state:
- Styling: use
StyleSheet.create(parsed once at load) or NativeWind (Tailwind for RN). Avoid inline style objects inrender— they are recreated every render and skip memoization. - Native modules:
- Prefer a well-maintained community package (
react-native-camera,react-native-vision-camera, etc.) over writing a custom native module - For Expo managed workflow: use an Expo config plugin; never eject just to add a single module
- When writing a custom module: implement in Swift (iOS) and Kotlin (Android); expose via the New Architecture (
TurboModules) if the project targets RN 0.73+
- Prefer a well-maintained community package (
- Performance pass:
- Use
React.memoon list item components; pass stable callbacks viauseCallback - Use
FlashList(@shopify/flash-list) instead ofFlatListfor long lists - Run the JS thread profiler in Flipper or React DevTools; target <16 ms per frame on mid-range Android
- Enable Hermes engine on both platforms (default since RN 0.70)
- Use
- Push notifications: use
expo-notifications(managed) or@notifee/react-native(bare); configure APNs entitlements on iOS and FCMgoogle-services.jsonon Android before writing any notification code - Build and submit:
- Expo: use EAS Build (
eas build) and EAS Submit (eas submit) witheas.jsonprofiles for dev/staging/prod - Bare RN: Fastlane + Xcode Cloud / Gradle for signing; store keystores and certs in a secrets manager, never in the repo
- Run
npx react-native doctorand fix all warnings before cutting a release
- Expo: use EAS Build (
Standards
| Area | Do | Do not |
|---|---|---|
| Components | Functional components + hooks only; React.memo for pure list items |
Class components in new code |
| Lists | FlashList with estimatedItemSize; keyExtractor returns stable IDs |
ScrollView wrapping a map() for lists longer than ~20 items |
| Images | expo-image or react-native-fast-image for caching; explicit width/height |
Raw <Image> from RN core for remote images (no caching) |
| Permissions | Request permissions at the moment of use with clear rationale strings | Request all permissions on app launch |
| Deep links | Configure universal links (iOS) and App Links (Android) in the same PR as the feature | Test deep links only on one platform |
| Secrets | expo-constants + EAS secrets, or react-native-config with .env git-ignored |
Hardcode API keys in JS source |
| Testing | Unit: Jest + @testing-library/react-native; E2E: Detox or Maestro |
Skip E2E because "it's slow to set up" |
Common mistakes to avoid
useEffectwith missing dependencies — causes stale closures; run ESLint withreact-hooks/exhaustive-depsand treat warnings as errors.- Calling
setStateon an unmounted component — return a cleanup function fromuseEffectthat cancels pending async work or sets acancelledflag. - Over-using
useEffectfor derived state — if a value can be computed from props/state synchronously, compute it in the render body or withuseMemo; do not store it in state and sync it with an effect. - Bridge-heavy operations on the JS thread — operations like image decoding, crypto, or JSON parsing of large payloads block the JS thread; offload to a
worklet(Reanimated) or a native module. - Not testing on a real mid-range Android device — iOS Simulator and high-end Android emulators mask performance issues that appear on real budget Android hardware.
- Ignoring Metro cache after native changes — after adding/removing native modules or changing
app.json, clear Metro cache:npx expo start --clearornpx react-native start --reset-cache. android:allowBackup="true"left as default — backs up sensitive shared preferences to Google Drive. Set tofalseor implement a customBackupAgent.
Output format
Typical feature deliverable structure:
src/
features/
<feature>/
screens/
<Feature>Screen.tsx # Root screen registered in navigator
components/
<Feature>Card.tsx # Memoized sub-component
hooks/
use<Feature>.ts # Data fetching + business logic
api/
<feature>Api.ts # Typed fetch/axios calls
types.ts # Feature-local TypeScript types
navigation/
RootNavigator.tsx # Typed navigator + RootStackParamList
store/
<feature>Store.ts # Zustand or RTK slice
__tests__/
features/
<feature>/
<Feature>Screen.test.tsx
Related checklists
.claude/checklists/security.md.claude/checklists/performance.md.claude/checklists/accessibility.md.claude/checklists/production.md
Related agents
.claude/agents/engineering/mobile-engineer.md.claude/agents/design/mobile-ux-specialist.md.claude/agents/quality/performance-engineer.md.claude/agents/quality/security-auditor.md.claude/agents/quality/accessibility-auditor.md