Instruction file imported from bernierllc/agents-environment-config (
.cursor/rules/stacks/react-native/expo-development.mdc). Copyright stays with the author.
React Native & Expo Development Rules
Command Usage
Workspace Commands
- ALWAYS use
pnpm devinstead ofexpo startdirectly - ALWAYS use
pnpm iosfor iOS development (includes Xcode version check) - ALWAYS use
pnpm androidfor Android development - ALWAYS use
pnpm webfor web development - ALWAYS use
pnpm web:buildfor production web builds - NEVER run
expo startdirectly - use workspace commands
EAS Build Commands
- Development builds:
pnpm eas:build:dev:*(simulator, device, local) - Staging builds:
pnpm eas:build:staging:* - Submit builds:
pnpm eas:submit:* - Update builds:
pnpm eas:update:* - Platform-specific: Add
:iosor:androidsuffix
Server Assumptions
- ASSUME Expo dev server is running on
http://localhost:8081 - ASSUME Metro bundler is running and watching for changes
- DO NOT start Expo servers unless explicitly requested
Development Workflow
- Test on actual devices when possible
- Use Expo Go app for quick testing
- Test on both iOS and Android when making platform-specific changes
- Use
pnpm prebuildbefore native builds - Check Xcode version automatically with
pnpm ios
Platform-Specific Files
React Native supports platform-specific file extensions:
.native.tsx- React Native specific implementations.ios.tsx- iOS specific implementations.android.tsx- Android specific implementations.web.tsx- Web specific implementations
Key Generation
Security Requirements
- ALWAYS use
randomUUID()fromexpo-cryptofor generating unique keys and identifiers - NEVER use
Math.random()or other insecure methods for key generation - IMPORT from
expo-cryptowhen generating UUIDs for React keys, database IDs, or any unique identifiers
// ✅ Good - using expo-crypto for key generation
import { randomUUID } from 'expo-crypto';
const generateKey = () => randomUUID();
const itemKey = randomUUID();
// ❌ Bad - using insecure methods
const badKey = Math.random().toString();
const anotherBadKey = Date.now().toString();
Testability by Design (MANDATORY)
Add testID to all interactive elements, dynamic state containers, and structural landmarks when building components. This is a build-time requirement, not a testing afterthought.
Required elements:
- Interactive:
Button,Pressable,TouchableOpacity,TextInput,Switch,Picker - Dynamic state: error messages, success banners, empty states, loading indicators, count badges
- Structural landmarks: screen root, modal container, list container, navigation bar
// ✅ REQUIRED when building components
import { View, TextInput, Pressable, Text } from 'react-native';
export function LoginForm({ onSubmit }: LoginFormProps) {
return (
<View testID="login-form">
<TextInput
testID="email-input"
value={email}
onChangeText={setEmail}
/>
<Pressable testID="login-button" onPress={handleLogin}>
<Text>Login</Text>
</Pressable>
{hasError && (
<Text testID="login-error">{errorMessage}</Text>
)}
</View>
);
}
Missing testID on required elements is a developer deficiency — fix it at the source, not in tests. Use randomUUID() from expo-crypto for IDs, but use descriptive strings for testID.
Testing Approach
- Prompt user to test on actual device or simulator
- Suggest using Expo Go for quick testing
- Recommend testing on both platforms for cross-platform features
File Locations
- Expo config:
app.jsonorapp.config.js - Native code:
ios/andandroid/directories (generated byprebuild) - Shared code:
src/directory - Platform-specific: Use file extensions (
.ios.tsx,.android.tsx)
References
- Development Workflow: See
general/development-workflow.mdc - TypeScript: See
languages/typescript/typing-standards.mdc
