Skip to content

React Native development with an AI agent works when the agent knows the pinned library versions and every change passes gates that need no human reader: type check, lint, Jest with React Native Testing Library, a Maestro flow on a device, and a release build. With those gates, the agent can own offline data, list performance, and biometric auth.

This page is for developers and tech leads who ship a React Native app to the App Store and Google Play. Your FlatList stutters on a mid-range Android phone once it passes a few hundred rows. The release build crashes on a native module that the development build never loaded. The offline screen from last week’s demo drops writes when the network flaps. None of this shows up on the simulator on your laptop, and all of it is library-specific work an agent does well once it is told which library and which version.

New to agent setup for mobile work? Start with the mobile development overview, which covers choosing a stack, installing its skills and verifying on simulators.

  • A version table and a project-instructions block that stop the agent from writing APIs from an older major
  • A scaffold prompt that produces an Expo app with tests and gates on day one
  • An offline-first prompt for react-native-mmkv v4 and TanStack Query, including mutations that survive an app kill
  • A FlashList v2 prompt, and the Android command that proves the jank is gone
  • A biometric gate prompt that falls back instead of locking users out
  • A verification table: which gate proves what, and who signs off
  • A map of the failures that never appear on the simulator, with recovery steps

Which React Native versions does this page assume?

Section titled “Which React Native versions does this page assume?”

Agents mix APIs from several majors because their training data spans them. Put the versions in the prompt or in the project instructions. The table lists what npm served on 2026-09-26.

PackageVersion on 2026-09-26What changes for the agent
react-native0.87.1 (Expo SDK 57 pins 0.86.3)The New Architecture is the only architecture: 0.82 removed the opt-out (React Native changelog)
expo57.0.25Install native packages with npx expo install, never by hand-editing versions
@shopify/flash-list2.3.2 (SDK 57 pins 2.0.2)v2 runs on the New Architecture only and takes no size estimates
react-native-mmkv4.3.2v4 is a Nitro Module: createMMKV() and storage.remove(), not new MMKV() and storage.delete()
@tanstack/react-query5.104.0Persist with @tanstack/react-query-persist-client and @tanstack/query-sync-storage-persister
@testing-library/react-native14.0.1v14 drops React 18, makes render and renderHook async, and needs the test-renderer package as a peer
jest-expo57.0.5The Jest preset Expo documents for unit tests; it mocks the native side of the Expo SDK

If you start a new app, start on Expo. The default create-expo-app template is TypeScript with Expo Router. SDK specifics, EAS, and over-the-air updates are on Expo development patterns; this page covers the React Native work that is the same with or without Expo.

Give the agent React Native context before the first prompt

Section titled “Give the agent React Native context before the first prompt”

Three things change the agent’s output more than any prompt wording: the version table above, a list of gates it must run, and domain skills or MCP servers that let it check its own work on a device.

Project instructions. Put this block in CLAUDE.md for Claude Code, in AGENTS.md for Codex, and in a project rule for Cursor. The content is identical in all three tools.

## React Native rules
- Expo SDK 57, React Native 0.86 (New Architecture only). Add native packages with `npx expo install`.
- react-native-mmkv v4: `createMMKV()`, `storage.remove()`. FlashList v2: no `estimatedItemSize`.
- Tests: Jest via `jest-expo` + @testing-library/react-native v14 with `test-renderer` (`await render(...)`). Not Vitest.
## Before you say a task is done
1. `npx tsc --noEmit`
2. `npx expo lint`
3. `npx jest --ci`
4. `npx expo install --check`
5. For UI changes: run the matching Maestro flow in `.maestro/` and paste the result.

Skills. Expo publishes official skills in expo/skills (2,612 GitHub stars on 2026-09-26); the per-tool install is on the Expo page. Callstack, a React Native consultancy (community, not Meta), publishes production skills such as react-native-best-practices and upgrading-react-native in callstackincubator/agent-skills. The cross-agent skills CLI (1.7.0) installs them for Claude Code, Codex, and Cursor alike:

Terminal window
npx skills@latest add callstackincubator/agent-skills --skill react-native-best-practices

Skills load only when a task needs them, so they cost little context. See installing and managing skills.

MCP servers. The Maestro CLI ships its own MCP server (maestro mcp), which lets the agent tap, scroll, and assert on a live emulator, simulator, or Android device while it works. For iOS builds and simulators, MobileBuildMCP (formerly XcodeBuildMCP) is covered on dev-server MCPs. Each MCP server adds its tool list to every session, so connect Maestro only in the app repository.

Terminal window
# after installing the Maestro CLI (Java 17+)
claude mcp add maestro -- maestro mcp

The Maestro README (fetched 2026-09-26) documents maestro mcp; the per-client lines above follow each tool’s standard stdio syntax. Check Maestro’s MCP setup docs if your client needs extra options.

Scaffold a React Native app with gates from day one

Section titled “Scaffold a React Native app with gates from day one”

Ask for the test harness and the gates in the scaffold prompt. An app that starts without them never gets them, and every later agent change is then unverifiable.

The prompt is the same in every tool; only the invocation differs. Save the prompt above as scaffold-prompt.txt, then start the agent with it.

Terminal window
claude "$(cat scaffold-prompt.txt)"

From v2.1.283 (both release channels on 2026-10-12), interactive sessions start in auto mode, where a classifier reviews each action; sessions with disableAutoMode set, models auto mode does not support, and claude -p (except on third-party providers or with telemetry off, where it starts in auto mode from v2.1.285) start in Manual and ask before each shell command. Either way, the npx installs are expected, and anything that writes outside the project is not.

Use the bare workflow (npx @react-native-community/cli@latest init) only when you need native code that config plugins cannot express. You then own the ios/ and android/ projects, and the native-drift failures below become yours.

Build an offline-first data layer that survives an app kill

Section titled “Build an offline-first data layer that survives an app kill”

“Add offline support” gets you an AsyncStorage wrapper that loses queued writes. Name the storage engine, the cache layer, and the durability requirement.

The persister glue should stay this small. If the agent writes more, it is probably reinventing the persister:

import { createMMKV } from 'react-native-mmkv';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
const storage = createMMKV({ id: 'query-cache' });
export const persister = createSyncStoragePersister({
storage: {
getItem: (key) => storage.getString(key) ?? null,
setItem: (key, value) => storage.set(key, value),
removeItem: (key) => {
storage.remove(key);
},
},
});

The durability question is the one the agent gets wrong: what happens when the app is killed mid-sync and reopened offline? Without setMutationDefaults, the restored mutation has no function to call and silently never replays. Test 1 in the prompt fails on exactly that bug, which is why it is in the prompt. If you need two-way relational sync rather than a replayed cache, that is a sync-engine decision (for example WatermelonDB); confirm the version you pick supports the New Architecture before you prompt for it.

Swapping the hot list to @shopify/flash-list recycles row views instead of mounting one per row. Version 2 changed the rules: it takes no size estimates and runs only on the New Architecture. As before, it keeps a separate recycling pool per getItemType value, which v2 relies on more because it measures rows itself. An agent trained on v1 adds estimatedItemSize, which v2 no longer needs.

A green test proves the list renders, not that it is smooth. To prove the performance claim, measure a release build on a real mid-range Android device before and after the change:

Terminal window
npx expo run:android --variant release
adb shell dumpsys gfxinfo com.example.app reset
# scroll the list for 20 seconds, then:
adb shell dumpsys gfxinfo com.example.app | grep -i "janky frames"

Replace com.example.app with your application ID. Paste both numbers into the pull request. If mixed row types have no getItemType, v2 recycles a tall card into a short slot and you see flicker or wrong heights, the v2 version of the old wrong-estimate jank.

Add a biometric gate that does not lock users out

Section titled “Add a biometric gate that does not lock users out”

Face ID works in the simulator with Enrolled switched on. On a real phone with no enrolled biometrics, or after too many failed attempts, a naive gate locks the user out. authenticateAsync from expo-local-authentication can fail because the user cancels, nothing is enrolled, the hardware is missing, the user is locked out, or the prompt times out.

The unit tests prove the branching. Then check each platform once on a physical device, because the simulator does not reproduce lockout.

Build a feature end to end in reviewable steps

Section titled “Build a feature end to end in reviewable steps”

For a feature built from scratch, drive the agent through ordered steps instead of one large prompt. Each step ends at a gate, so you accept the step on evidence and never read the full diff at the end.

  1. Screen and types. “Create a ProjectList route with a typed useProjects() TanStack Query hook that returns mock data.” Gate: npm run typecheck passes.

  2. Real data and every state. “Replace the mock with a fetch to /api/projects. Add a loading skeleton, an empty state, and an error state with a Retry button that calls refetch().” Gate: RNTL tests for loading, rows, empty ([]), and retry, run with npm test.

  3. Performance. Paste the FlashList prompt. Gate: the gfxinfo janky-frames numbers before and after, on a release build.

  4. Behaviour on a device. “Write .maestro/project-list.yaml that opens the list, scrolls to the end, and asserts the last row. Add an Android-only flow that runs setAirplaneMode: enabled, pulls to refresh, and asserts the error state and Retry.” Gate: maestro test .maestro/ passes. Maestro’s airplane-mode commands are Android-only because of iOS simulator restrictions.

How do you verify React Native agent output without reading every line?

Section titled “How do you verify React Native agent output without reading every line?”

Each gate answers one question. Make the agent run all of them and attach the output to the pull request, the same evidence bundle the rest of the site uses.

GateCommandWhat it proves
Typesnpx tsc --noEmitNo API from the wrong major slipped in (new MMKV() fails here)
Lintnpx expo lintHook rules and unused code
Unit and componentnpx jest --ciBranches: offline replay, biometric fallbacks, empty and error states
Dependency alignmentnpx expo install --checkNo native package drifted from the SDK’s pinned version
Behaviour on a devicemaestro test .maestro/Navigation, gestures, and offline states on a real runtime
Release buildnpx expo run:ios --configuration Release, npx expo run:android --variant releaseThe code path that users run, with __DEV__ false
Performanceadb shell dumpsys gfxinfo <application-id>Janky frames before and after, on a named device

Who signs off. The developer who wrote the task owns the acceptance criteria and the Maestro flows. A change to an existing flow or test needs a second person’s approval, because an agent that edits the test to pass has removed the proof; see protect the oracle. Device matrices and device farms are on mobile app testing.

Debug release-only bugs with React Native DevTools

Section titled “Debug release-only bugs with React Native DevTools”

React Native DevTools is the default debugger for Hermes apps and replaced the Chrome DevTools flow in React Native 0.76. Press j in the terminal where Metro or Expo runs. Flipper is not the answer: its README names v0.239.0 as the last release with React Native support. Reactotron (reactotron-react-native 5.3.1 on npm) is a common add-on for persistent network and state logs.

Ship React Native builds to TestFlight and Google Play

Section titled “Ship React Native builds to TestFlight and Google Play”

On Expo, EAS Build and EAS Submit own the native compile, credentials, and store upload; the fingerprint-gated pipeline and staged over-the-air updates are on Expo development patterns. On the bare workflow you wire Fastlane into CI yourself, and the agent will write a lot of YAML. Constrain it:

Review the workflow against CI/CD pipeline patterns before you merge it. Never let the agent trigger a production upload to test the workflow.

What breaks in React Native that the simulator never shows?

Section titled “What breaks in React Native that the simulator never shows?”

Did this guide help you?

One tap, no sign-in.

not at all a lot