React Native + Expo
Project context
This is a React Native app using Expo's managed workflow with file-based routing via expo-router. We target iOS and Android from a shared codebase. EAS handles builds and submissions; we don't manage native projects manually.
Stack
- Expo SDK 51+ (managed workflow)
- React Native 0.74+
- TypeScript strict mode
expo-routerv3+ for file-based routing- NativeWind v4 for Tailwind-style styling
expo-imagefor image rendering (not the bare RN<Image>)react-native-reanimatedfor animations- React Query (TanStack) for server state
zustandfor global UI stateexpo-secure-storefor tokens / sensitive data- pnpm or bun
Folder structure
app/
_layout.tsx — root layout (Stack / Tabs / Drawer)
(tabs)/ — tab group
(auth)/ — auth-only routes
[id].tsx — dynamic route
components/ — reusable components, organized by feature
hooks/
lib/ — utilities, API client
constants/ — theme, sizes, etc.
assets/ — images, fonts
Routing
- File-based routing only — never
react-navigationdirectly - Layout files (
_layout.tsx) define stack/tabs/drawer for a segment - Use
useLocalSearchParams()for dynamic params (typed) - Use
<Link>for navigation;router.push()for programmatic - Use route groups
(name)/to share a layout without affecting URLs
// app/_layout.tsx
import { Stack } from 'expo-router'
export default function RootLayout() {
return <Stack screenOptions={{ headerShown: false }} />
}
Components
- Functional components, named exports only
- Props typed inline;
children: React.ReactNode - Use
expo-image's<Image>over RN's built-in (better caching, perf) - Use
expo-router's<Link>overPressable-wrapped navigations
Styling with NativeWind
- All styles via
className(NativeWind compiles to RN styles) - Use semantic Tailwind classes (
bg-background,text-on-surface) defined intailwind.config.ts - For platform-specific styles:
className="ios:pt-12 android:pt-8" - For dark mode: configure in
_layout.tsx; usedark:variants
Platform differences
- Use
Platform.OS === 'ios'only when behavior must diverge - Prefer
.ios.tsx/.android.tsxfile extensions for substantially different implementations - Always test critical flows on both platforms before claiming done
Async storage
- Tokens / sensitive data:
expo-secure-store(encrypted) - Cache / non-sensitive data:
@react-native-async-storage/async-storage - Form state, ephemeral UI: in-memory only
Networking
- One API client at
lib/api.ts, typed - Use TanStack Query for server state — handles caching, retries, focus refetch
- Set sensible
staleTimeon every query — mobile is sensitive to network thrash - Handle offline explicitly:
react-native-netinfofor network state, optimistic updates for mutations
Performance
- Use
expo-imagefor caching; specifycontentFitandplaceholder - Use
FlatList(orFlashList) for any list over ~20 items - Set
keyExtractorandgetItemLayoutwhen item heights are known - Avoid inline functions in
renderItem— extract to a memoized component - Use
useCallbackandReact.memofor list-row components specifically (this is one place memoization actually pays off)
EAS / builds
eas build --profile developmentfor dev client buildseas build --profile previewfor internal testingeas build --profile productionfor App Store / Play Storeeas submitfor store uploads- Don't run
npx react-native run-iosdirectly — that's bare workflow
Patterns to avoid
react-navigationdirect usage —expo-routerwraps it; use the router- Class components — hooks only
StyleSheet.createwhen NativeWind would do — keep all styles inclassNameImagefrom React Native — useexpo-imageAsyncStoragefor tokens — useexpo-secure-store- Synchronous storage reads in render — always async, behind a state machine
- Mixing
useEffectwith navigation — listen torouterevents instead
Testing
- Unit tests with Jest +
@testing-library/react-native - Detox or Maestro for end-to-end flows on a real device
- Smoke-test on both iOS and Android before declaring done
Tooling
pnpm startornpx expo start— dev servereas build— produce installable buildseas update— over-the-air updatespnpm lint— ESLintpnpm typecheck— TypeScript
AI behavioral rules
- Default to
expo-routerfor navigation; never proposereact-navigationdirectly - Use NativeWind classes; don't reach for
StyleSheet.createunless dynamic - Use
expo-image, not RN'sImage - Store tokens in
expo-secure-store, neverAsyncStorage - Test changes on both iOS and Android before claiming done
- Don't add native modules without surfacing the eject implications
- Run lint, typecheck, and tests before declaring a task complete