React Native Development Patterns
Your FlatList janks on mid-tier Android the moment the list passes a few hundred rows, your release build crashes on a native module the Expo dev client never exercised, and the offline screen your PM demoed last week silently drops writes when the network flaps. None of these reproduce on the simulator on your M-series Mac. This is the gap between a React Native demo and a shippable app - and it is exactly where an AI agent earns its keep, because the fixes are mechanical, library-specific, and tedious to type by hand.
This recipe walks the production workflow: scaffold on Expo (the recommended path for new apps), wire navigation and offline-first state with current libraries, fix the list that janks, and ship through CI to TestFlight and Play. Every step is driven by a prompt you can paste into Cursor, Claude Code, or Codex.
What You’ll Walk Away With
Section titled “What You’ll Walk Away With”- A reusable scaffold prompt that produces an Expo + TypeScript + React Navigation + Vitest/RNTL project, identical across all three tools
- A copy-paste prompt for an offline-first store using
react-native-mmkv+ TanStack Query (or WatermelonDB when you need real sync) - A prompt that turns a janky
FlatListinto a@shopify/flash-listv2 screen with correct view recycling and pagination - A biometric auth-flow prompt built on
expo-local-authentication+expo-secure-store - A “When This Breaks” playbook for the failure modes that never show up on the simulator: Metro cache, Hermes vs JSC, New Architecture interop, and native build drift
Scaffolding a New App
Section titled “Scaffolding a New App”npx react-native init and @react-native-community/cli init are no longer the recommended starting point - the React Native docs now point new apps at a framework, and Expo is the default. Give the agent the full stack up front so it picks compatible versions instead of guessing.
Open Agent mode (Cmd/Ctrl + I) and paste the scaffold prompt below. Cursor runs npx create-expo-app@latest in the integrated terminal, then edits the generated files. Use a checkpoint before you accept so you can roll back the whole scaffold in one click if the navigation wiring is wrong.
Run the scaffold prompt in the terminal. Claude Code executes the create-expo-app command, reads the generated tree, and adds navigation + the test harness in follow-up edits:
claude "Scaffold a React Native app with create-expo-app (TypeScript template), add React Navigation native-stack + bottom-tabs, and set up Vitest with React Native Testing Library"Pass the prompt positionally to start an interactive session, or use codex exec for a one-shot scaffold in CI:
codex "Scaffold a React Native (Expo, TypeScript) app with React Navigation and a Vitest + React Native Testing Library setup"For a hands-off run, codex exec --full-auto "<same prompt>". To offload the scaffold to a cloud worktree, use codex cloud exec with a target environment.
If you genuinely need the bare workflow (a custom native module the config plugins do not cover), tell the agent to use npx @react-native-community/cli@latest init instead - but note it is no longer the default and you take on the native build maintenance yourself.
Offline-First State
Section titled “Offline-First State”The screen that demos perfectly and then loses data is almost always a state layer that assumes the network is up. Be specific about the storage engine and the sync strategy - “add offline support” gets you a vague AsyncStorage wrapper; naming react-native-mmkv and TanStack Query gets you something that survives a flaky train tunnel.
When the data model is relational and you need real two-way sync (not just cache replay), point the agent at WatermelonDB instead:
A short example of what the agent should produce for the MMKV persister - keep it this size, it is the load-bearing glue, not a tutorial:
import { MMKV } from 'react-native-mmkv';import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
const storage = new MMKV();const persister = { persistClient: (c) => storage.set('rq-cache', JSON.stringify(c)), restoreClient: () => { const v = storage.getString('rq-cache'); return v ? JSON.parse(v) : undefined; }, removeClient: () => storage.delete('rq-cache'),};Evaluate the output on one axis the agent often gets wrong: does the mutation queue dedupe replays? Ask “what happens if the app is killed mid-sync and reopened offline?” - if the answer is hand-wavy, the queue is not durable yet.
Fixing the List That Janks
Section titled “Fixing the List That Janks”FlatList is fine until it is not. The single highest-leverage performance change in most RN apps is swapping the hot list to @shopify/flash-list, which recycles views instead of mounting one per row. Pin v2 (@shopify/flash-list v2 is a ground-up rewrite that runs only on the New Architecture) and drop the v1 estimatedItemSize / estimatedListSize props entirely - v2 measures items synchronously and removed them (“No Longer Needed!”). Do not let the agent stop at a drop-in swap; the real win comes from stable recycling and a correct getItemType for mixed rows.
The evaluation question here: ask the agent how rows recycle. If your list mixes row shapes (headers, ads, different card types) and there is no getItemType, v2 will recycle a tall card into a short slot and you’ll see flicker or wrong heights - the modern equivalent of the v1 wrong-estimate jank. For very long lists, also check drawDistance is tuned rather than left at a guess.
Biometric Authentication Flow
Section titled “Biometric Authentication Flow”Auth is the other place demos lie: Face ID works in the simulator’s “Enrolled” toggle and then a real device with no enrolled biometrics throws. Name the Expo modules so the agent handles the unenrolled and fallback paths.
End-to-End: From Empty Repo to a Tested Feature
Section titled “End-to-End: From Empty Repo to a Tested Feature”For a genuinely sequential task - building a feature from scratch - drive the agent through ordered steps rather than one mega-prompt, so you can inspect each artifact before the next builds on it. The flow below is the same in Cursor, Claude Code, and Codex; only the invocation differs (Agent mode, claude "...", or codex "...").
-
Generate the screen and its types. “Create a
ProjectListscreen with a typed route inRootStackParamList, fetching projects via auseProjects()TanStack Query hook. Stub the hook to return mock data for now.” Expected output: the screen, the param-list entry, and a typed hook. -
Wire real data and the empty/error states. “Replace the mock with a real fetch against
/api/projects, add loading skeletons, an empty state, and an error state with a retry button that callsrefetch().” Inspect that the error state actually surfaces network failures, not just throws. -
Make the list fast. Paste the FlashList prompt from above against the
ProjectList. Verify it targets v2 with no estimate props and that mixed row types get agetItemType. -
Test it. “Write React Native Testing Library tests for
ProjectList: it renders the loading state, renders rows on success, shows the empty state for[], and callsrefetchwhen the retry button is pressed. Mock the query hook.” Run the suite and confirm the empty-state assertion is real, not a snapshot.
Debugging in 2026: React Native DevTools, Not Flipper
Section titled “Debugging in 2026: React Native DevTools, Not Flipper”If an article tells you to set up Flipper, it is out of date. Flipper’s native React Native integration was deprecated in RN 0.73 and removed after it; Meta archived the Flipper repo in late 2025. The default debugger is now React Native DevTools - press j in the Metro terminal (or shake the device and pick “Open DevTools”) to launch a Hermes-backed Chrome DevTools UI with no install step. For richer network/state inspection, Reactotron is the common add-on.
Shipping: CI/CD to TestFlight and Play
Section titled “Shipping: CI/CD to TestFlight and Play”Builds and store uploads are the most mechanical part of the whole pipeline, which makes them ideal to hand off. On Expo, EAS Build owns the native compile and submission; on bare RN you wire Fastlane into GitHub Actions yourself.
In Agent mode: “Add an EAS Build + EAS Submit setup for this Expo app and a GitHub Actions workflow that builds iOS and Android on tags and submits to TestFlight and Play internal testing.” Cursor scaffolds eas.json, the workflow, and explains which secrets to add.
claude "Create eas.json with development, preview, and production profiles, plus a GitHub Actions workflow that runs eas build --platform all and eas submit on version tags. List the EXPO_TOKEN and store credentials I need as repo secrets."codex "Set up EAS Build/Submit and a GitHub Actions release workflow for this Expo app: build iOS + Android on tags, submit to TestFlight and Play internal track, and pin actions/checkout to the current major."For a longer, parallelizable run that opens a PR with the workflow, delegate it to codex cloud exec.
When This Breaks
Section titled “When This Breaks”These are the failure modes that never appear on your simulator - and the recovery steps to hand the agent.
What’s Next
Section titled “What’s Next”- Explore Flutter patterns if you’re weighing Dart against React Native
- Learn the Expo workflows that this recipe builds on - EAS Build, updates, and config plugins
- Wire your app to a backend with the Backend integration patterns