**Status**: Production Ready **Last Updated**: 2026-01-21 **Dependencies**: Node.js 20.19.4+, Expo CLI, Xcode 16.1+ (iOS) **Latest Versions**: react-native@0.81.5, expo@~54.0.31, react@19.2.3 * * * ```
# Create new Expo app with React Native 0.76+ npx create-expo-app@latest my-app cd my-app # Install latest dependencies npx expo install react-native@latest expo@latest
# Check if New Architecture is enabled (should be true by default) npx expo config --type introspect | grep newArchEnabled
# Start Expo dev server npx expo start # Press 'i' for iOS simulator # Press 'a' for Android emulator # Press 'j' to open React Native DevTools (NOT Chrome debugger!)
console.log() - use DevTools Console# This will FAIL in 0.82+ / SDK 55+: # gradle.properties (Android) newArchEnabled=false # ❌ Ignored, build fails # iOS RCT_NEW_ARCH_ENABLED=0 # ❌ Ignored, build fails
propTypes completely. No runtime validation, no warnings - silently ignored.import PropTypes from 'prop-types'; function MyComponent({ name, age }) { return <Text>{name} is {age}</Text>; } MyComponent.propTypes = { // ❌ Silently ignored in React 19 name: PropTypes.string.isRequired, age: PropTypes.number }; `**After (Use TypeScript):**` type MyComponentProps = { name: string; age?: number; }; function MyComponent({ name, age }: MyComponentProps) { return <Text>{name} is {age}</Text>; } `**Migration:**` # Use React 19 codemod to remove propTypes npx @codemod/react-19 upgrade
forwardRef no longer needed - pass ref as a regular prop.import { forwardRef } from 'react'; const MyInput = forwardRef((props, ref) => { // ❌ Deprecated return <TextInput ref={ref} {...props} />; }); `**After (React 19):**` function MyInput({ ref, ...props }) { // ✅ ref is a regular prop return <TextInput ref={ref} {...props} />; }
AppDelegate.swift instead of Objective-C AppDelegate.mm.ios/MyApp/ ├── main.m # ❌ Removed ├── AppDelegate.h # ❌ Removed └── AppDelegate.mm # ❌ Removed `**New Structure:**` // ios/MyApp/AppDelegate.swift ✅ import UIKit import React @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, ...) -> Bool { // App initialization return true } } `**Migration (0.76 → 0.77):** When upgrading existing projects, you **MUST** add this line:` // Add to AppDelegate.swift during migration import React import ReactCoreModules RCTAppDependencyProvider.sharedInstance() // ⚠️ CRITICAL: Must add this!
console.log() output.# console.log() appeared in Metro terminal $ npx expo start > LOG Hello from app! # ✅ Appeared here `**After (0.77+):**` # console.log() does NOT appear in Metro terminal $ npx expo start # (no logs shown) # ❌ Removed # Workaround (temporary, will be removed): $ npx expo start --client-logs # Shows logs, deprecated
chrome://inspect) removed. Use React Native DevTools instead.# ❌ This no longer works: # Open Dev Menu → "Debug" → Chrome DevTools opens `**New Method (0.76+):**` # Press 'j' in CLI or Dev Menu → "Open React Native DevTools" # ✅ Uses Chrome DevTools Protocol (CDP) # ✅ Reliable breakpoints, watch values, stack inspection # ✅ JS Console (replaces Metro logs)
# JSC removed from React Native core # If you still need JSC (rare): npm install @react-native-community/javascriptcore
// ❌ Deep imports deprecated import Button from 'react-native/Libraries/Components/Button'; import Platform from 'react-native/Libraries/Utilities/Platform'; `**After:**` // ✅ Import only from 'react-native' import { Button, Platform } from 'react-native';
// app.json or app.config.js { "expo": { "android": { // This setting is now IGNORED - edge-to-edge always enabled "edgeToEdgeEnabled": false // ❌ No effect in SDK 54+ } } }
react-native-safe-area-context.import { SafeAreaView } from 'react-native-safe-area-context'; function App() { return ( <SafeAreaView style={{ flex: 1 }}> {/* Content respects system bars */} </SafeAreaView> ); }
display: contents<View style={{ display: 'contents' }}> {/* This View disappears, but Text still renders */} <Text>I'm still here!</Text> </View>
boxSizing// Default: padding/border inside box <View style={{ boxSizing: 'border-box', // Default width: 100, padding: 10, borderWidth: 2 // Total width: 100 (padding/border inside) }} /> // Content-box: padding/border outside <View style={{ boxSizing: 'content-box', width: 100, padding: 10, borderWidth: 2 // Total width: 124 (100 + 20 padding + 4 border) }} />
mixBlendMode + isolation<View style={{ backgroundColor: 'red' }}> <View style={{ mixBlendMode: 'multiply', // 16 modes available backgroundColor: 'blue' // Result: purple (red × blue) }} /> </View> // Prevent unwanted blending: <View style={{ isolation: 'isolate' }}> {/* Blending contained within this view */} </View>
multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosityoutline Propertiesborder):<View style={{ outlineWidth: 2, outlineStyle: 'solid', // solid | dashed | dotted outlineColor: 'blue', outlineOffset: 4, // Space between element and outline outlineSpread: 2 // Expand outline beyond offset }} />
// Load XML drawable at build time import MyIcon from './assets/my_icon.xml'; <Image source={MyIcon} style={{ width: 40, height: 40 }} /> // Or with require: <Image source={require('./assets/my_icon.xml')} style={{ width: 40, height: 40 }} />
useActionState (replaces form patterns)import { useActionState } from 'react'; function MyForm() { const [state, submitAction, isPending] = useActionState( async (prevState, formData) => { // Async form submission const result = await api.submit(formData); return result; }, { message: '' } // Initial state ); return ( <form action={submitAction}> <TextInput name="email" /> <Button disabled={isPending}> {isPending ? 'Submitting...' : 'Submit'} </Button> {state.message && <Text>{state.message}</Text>} </form> ); } `#### 2\. `useOptimistic` (optimistic UI updates)` import { useOptimistic } from 'react'; function LikeButton({ postId, initialLikes }) { const [optimisticLikes, addOptimisticLike] = useOptimistic( initialLikes, (currentLikes, amount) => currentLikes + amount ); async function handleLike() { addOptimisticLike(1); // Update UI immediately await api.like(postId); // Then update server } return ( <Button onPress={handleLike}> ❤️ {optimisticLikes} </Button> ); } `#### 3\. `use` (read promises/contexts during render)` import { use } from 'react'; function UserProfile({ userPromise }) { // Read promise directly during render (suspends if pending) const user = use(userPromise); return <Text>{user.name}</Text>; }
j in CLIpropTypes just doesn't work Source: React 19 Upgrade Guide Why It Happens: React 19 removed runtime propTypes validation Prevention: Use TypeScript instead, run npx @codemod/react-19 upgrade to removeWarning: forwardRef is deprecated Source: React 19 Upgrade Guide Why It Happens: React 19 allows ref as a regular prop Prevention: Remove forwardRef wrapper, pass ref as prop directlynewArchEnabled=false Source: React Native 0.82 Release Notes Why It Happens: Legacy architecture completely removed from codebase Prevention: Migrate to New Architecture before upgrading to 0.82+Fabric component descriptor provider not found for component Source: New Architecture Migration Guide Why It Happens: Component not compatible with New Architecture (Fabric) Prevention: Update library to New Architecture version, or use interop layer (0.76-0.81)TurboModule '[ModuleName]' not found Source: New Architecture Migration Guide Why It Happens: Native module needs New Architecture support (TurboModules) Prevention: Update library to support TurboModules, or use interop layer (0.76-0.81)RCTAppDependencyProvider not found Source: React Native 0.77 Release Notes Why It Happens: When migrating from Objective-C to Swift template Prevention: Add RCTAppDependencyProvider.sharedInstance() to AppDelegate.swiftconsole.log() doesn't show in terminal Source: React Native 0.77 Release Notes Why It Happens: Metro log forwarding removed in 0.77 Prevention: Use React Native DevTools Console (press 'j'), or --client-logs flag (temporary)Module not found: react-native/Libraries/... Source: React Native 0.80 Release Notes Why It Happens: Internal paths deprecated, strict API enforced Prevention: Import only from 'react-native', not deep pathsredux + redux-thunk incompatible with New Architecture Prevention: Use Redux Toolkit (@reduxjs/toolkit) insteadi18n-js not fully compatible with New Architecture Prevention: Use react-i18next insteadnull Source: CodePush GitHub Issues Why It Happens: Known incompatibility with New Architecture Prevention: Avoid CodePush with New Architecture, or wait for official supportModule not found: expo-file-system/legacy Source: Expo SDK 54 Changelog, GitHub Issue #39056 Why It Happens: Legacy API removed in SDK 55, must migrate to new File/Directory class API Prevention: Migrate to new API before upgrading to SDK 55expo-file-systemexpo-file-system/legacy, new API at expo-file-system (default)import * as FileSystem from 'expo-file-system/legacy'; await FileSystem.writeAsStringAsync(uri, content); `**New Code (SDK 54+ new API):**` import { File } from 'expo-file-system'; const file = new File(uri); await file.writeString(content);
Module not found: expo-av Source: Expo SDK 54 Changelog, expo-av GitHub Why It Happens: Package deprecated in SDK 53, removed in SDK 55 Prevention: Migrate to expo-audio and expo-video before SDK 55expo-video introducedexpo-audio introduced, expo-av deprecatedexpo-av (no patches)expo-av removed// OLD: expo-av import { Audio } from 'expo-av'; const { sound } = await Audio.Sound.createAsync(require('./audio.mp3')); await sound.playAsync(); // NEW: expo-audio import { useAudioPlayer } from 'expo-audio'; const player = useAudioPlayer(require('./audio.mp3')); player.play(); `**Migration - Video:**` // OLD: expo-av import { Video } from 'expo-av'; <Video source={require('./video.mp4')} /> // NEW: expo-video import { VideoView } from 'expo-video'; <VideoView source={require('./video.mp4')} />
# NativeWind does not support Reanimated v4 yet (as of Jan 2026) # If using NativeWind, must stay on Reanimated v3 npm install react-native-reanimated@^3
react-native-worklets (required for v4)babel-preset-expo (auto-configured)hermes::vm::JSObject::putComputed_RJS hermes::vm::arrayPrototypePush
:hermes_enabled flag in Podfile when using New Architecture on iOS Prevention: Add explicit Hermes flag to ios/Podfileexpo-updates# ios/Podfile use_frameworks! :linkage => :static ENV['HERMES_ENABLED'] = '1' # ⚠️ CRITICAL: Must be explicit with New Arch + expo-updates
# Check current version npx react-native --version # Upgrade to 0.81 first (last version with interop layer) npm install react-native@0.81 npx expo install --fix `### Step 2: Enable New Architecture (if not already)` # Android (gradle.properties) newArchEnabled=true # iOS RCT_NEW_ARCH_ENABLED=1 bundle exec pod install # Rebuild npm run ios npm run android
# Replace Redux with Redux Toolkit npm uninstall redux redux-thunk npm install @reduxjs/toolkit react-redux # Replace i18n-js with react-i18next npm uninstall i18n-js npm install react-i18next i18next # Update React Navigation (if old version) npm install @react-navigation/native@latest `### Step 4: Test Thoroughly` # Run on both platforms npm run ios npm run android # Test all features: # - Navigation # - State management (Redux) # - API calls # - Deep linking # - Push notifications `### Step 5: Migrate to React 19 (if upgrading to 0.78+)` # Run React 19 codemod npx @codemod/react-19 upgrade # Manually verify: # - Remove all propTypes declarations # - Remove forwardRef wrappers # - Update to new hooks (useActionState, useOptimistic) `### Step 6: Upgrade to 0.82+` # Only after testing with New Architecture enabled! npm install react-native@0.82 npx expo install --fix # Rebuild npm run ios npm run android
# Follow upgrade helper # https://react-native-community.github.io/upgrade-helper/ # Select: 0.76 → 0.77 # CRITICAL: Add this line to AppDelegate.swift RCTAppDependencyProvider.sharedInstance()
import { useActionState } from 'react'; function LoginForm() { const [state, loginAction, isPending] = useActionState( async (prevState, formData) => { try { const user = await api.login(formData); return { success: true, user }; } catch (error) { return { success: false, error: error.message }; } }, { success: false } ); return ( <View> <form action={loginAction}> <TextInput name="email" placeholder="Email" /> <TextInput name="password" secureTextEntry /> <Button disabled={isPending}> {isPending ? 'Logging in...' : 'Login'} </Button> </form> {!state.success && state.error && ( <Text style={{ color: 'red' }}>{state.error}</Text> )} </View> ); }
// Define prop types with TypeScript type ButtonProps = { title: string; onPress: () => void; disabled?: boolean; variant?: 'primary' | 'secondary'; }; function Button({ title, onPress, disabled = false, variant = 'primary' }: ButtonProps) { return ( <Pressable onPress={onPress} disabled={disabled} style={[styles.button, styles[variant]]} > <Text style={styles.text}>{title}</Text> </Pressable> ); }
// Glowing button with outline and blend mode function GlowButton({ title, onPress }) { return ( <Pressable onPress={onPress} style={{ backgroundColor: '#3b82f6', padding: 16, borderRadius: 8, // Outline doesn't affect layout outlineWidth: 2, outlineColor: '#60a5fa', outlineOffset: 4, // Blend with background mixBlendMode: 'screen', isolation: 'isolate' }} > <Text style={{ color: 'white', fontWeight: 'bold' }}> {title} </Text> </Pressable> ); }
./scripts/check-rn-version.sh # Output: ✅ React Native 0.82 - New Architecture mandatory # Output: ⚠️ React Native 0.75 - Upgrade to 0.76+ recommended
// This no longer works in Expo Go (SDK 52+): { "jsEngine": "jsc" // ❌ Ignored, Hermes only } `**Google Maps Removed from Expo Go (SDK 53+):**` # Must use custom dev client for Google Maps npx expo install expo-dev-client npx expo run:android
import { fetch } from 'expo/fetch'; // Standards-compliant fetch for Workers/Edge runtimes const response = await fetch('https://api.example.com/data'); `**React Navigation v7:**` npm install @react-navigation/native@^7.0.0
{ "dependencies": { "react": "^19.2.3", "react-native": "^0.81.5", "expo": "~54.0.31", "@react-navigation/native": "^7.0.0", "@reduxjs/toolkit": "^2.0.0", "react-i18next": "^15.0.0" }, "devDependencies": { "@types/react": "^19.0.0", "typescript": "^5.7.0" } }
npx @codemod/react-19 upgrade.npx expo start --client-logs (temporary workaround).RCTAppDependencyProvider.sharedInstance() to AppDelegate.swift. See Swift migration section.redux + redux-thunk. Install @reduxjs/toolkit.react-native/Libraries/*)references/new-architecture-errors.md for build errorsreferences/react-19-migration.md for React 19 issues