Skip to content

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.

  • 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 FlatList into a @shopify/flash-list v2 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

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.

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.

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.

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.

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 "...").

  1. Generate the screen and its types. “Create a ProjectList screen with a typed route in RootStackParamList, fetching projects via a useProjects() TanStack Query hook. Stub the hook to return mock data for now.” Expected output: the screen, the param-list entry, and a typed hook.

  2. 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 calls refetch().” Inspect that the error state actually surfaces network failures, not just throws.

  3. 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 a getItemType.

  4. 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 calls refetch when 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.

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.

These are the failure modes that never appear on your simulator - and the recovery steps to hand the agent.