Setting up a React Native development environment can be tricky, especially for iOS development. Think of it like setting up a workshop: you need different tools depending on what you are building. iOS development requires Apple’s proprietary tools (Xcode, CocoaPods) that only run on macOS — there is no workaround for this at the local level. Android development is more platform-agnostic, but the toolchain (Android Studio, JDK, SDK, emulator with hardware acceleration) has many moving parts that must all agree on versions. This module walks you through every step on all platforms.What You’ll Learn:
Before setting up, choose your development approach. This is one of the most consequential decisions in a React Native project, and it is not as binary as it once was. Expo has evolved from “the training wheels option” to a production-grade toolchain used by companies like Shopify and Discord. The bare CLI remains necessary for brownfield integrations (adding React Native screens to an existing Swift/Kotlin app) and for teams that need full control over native build configuration.
┌─────────────────────────────────────────────────────────────────────────────┐│ Expo vs React Native CLI │├─────────────────────────────────────────────────────────────────────────────┤│ ││ Expo (Recommended for Most Projects) ││ ──────────────────────────────────── ││ ✅ Zero native code configuration ││ ✅ Expo Go app for instant testing ││ ✅ EAS Build for cloud builds ││ ✅ Over-the-air updates ││ ✅ Pre-built native modules ││ ✅ Works on Windows/Linux for iOS (via EAS) ││ ⚠️ Some native modules require "development builds" ││ ││ React Native CLI (Bare Workflow) ││ ──────────────────────────────── ││ ✅ Full native code access ││ ✅ Any native module/SDK ││ ✅ Custom native code ││ ⚠️ Requires Xcode (macOS) for iOS ││ ⚠️ More complex setup ││ ⚠️ Manual native configuration ││ ││ Recommendation: ││ Start with Expo. Use "development builds" when you need custom ││ native code. You get the best of both worlds. ││ │└─────────────────────────────────────────────────────────────────────────────┘
React Native requires Node.js 18 or newer. Node is the JavaScript runtime that powers Metro (the bundler), the development server, and all the CLI tooling. It does not run on the device itself — Hermes or JavaScriptCore handles that. Think of Node as the backstage crew: essential during development, invisible in production.
macOS
Windows
Linux
# Using Homebrew (recommended)brew install node# Or using nvm (Node Version Manager)curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bashsource ~/.zshrc # or ~/.bashrcnvm install 20nvm use 20nvm alias default 20# Verify installationnode --version # Should be v18.x or highernpm --version # Should be v9.x or higher
# Option 1: Download installer from nodejs.org# https://nodejs.org/en/download/# Option 2: Using Chocolateychoco install nodejs-lts# Option 3: Using nvm-windows# Download from: https://github.com/coreybutler/nvm-windows/releasesnvm install 20nvm use 20# Verify installationnode --versionnpm --version
# Using nvm (recommended)curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bashsource ~/.bashrcnvm install 20nvm use 20nvm alias default 20# Or using NodeSourcecurl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -sudo apt-get install -y nodejs# Verify installationnode --versionnpm --version
# Start the development servernpx expo start# Options in the terminal:# Press 'i' - Open iOS simulator (macOS only)# Press 'a' - Open Android emulator# Press 'w' - Open in web browser# Press 'r' - Reload app# Press 'm' - Toggle menu# Press 'j' - Open debugger# Or scan the QR code with:# - iOS: Camera app# - Android: Expo Go app
# Install from Mac App Store (recommended)# Search for "Xcode" in App Store# Or install via command linexcode-select --install# After installation, open Xcode and accept the licensesudo xcodebuild -license accept# Install additional components when prompted
# Install command line toolsxcode-select --install# Verify installationxcode-select -p# Should output: /Applications/Xcode.app/Contents/Developer# If you have multiple Xcode versionssudo xcode-select --switch /Applications/Xcode.app
CocoaPods is the dependency manager for iOS native dependencies — it is to iOS what Gradle is to Android. Every time you install a React Native library that includes native iOS code (camera, maps, push notifications), CocoaPods links those native modules into your Xcode project. If you skip pod install after adding a library, the JavaScript side will try to call native code that does not exist, resulting in a “module not found” crash at runtime.
# Install CocoaPods using Ruby gemsudo gem install cocoapods# Or using Homebrew (recommended)brew install cocoapods# Verify installationpod --version# Setup CocoaPods (first time only)pod setup
# Open Xcodeopen -a Xcode# Go to: Xcode → Settings → Platforms# Click '+' and install iOS simulators# Or via command linexcodebuild -downloadPlatform iOS# List available simulatorsxcrun simctl list devices
# Create a test projectnpx react-native@latest init TestProjectcd TestProject# Run on iOS simulatornpx react-native run-ios# Or with Exponpx create-expo-app@latest test-expocd test-exponpx expo run:ios
React Native requires JDK 17. Android builds use Gradle, which compiles your Java/Kotlin native code and packages the JavaScript bundle into an APK or AAB. The JDK version must match what Gradle expects — using JDK 11 or JDK 21 instead of 17 will produce cryptic build failures. This is one of the most common Android setup pitfalls: the version mismatch error messages rarely tell you “wrong JDK version” directly.
macOS
Windows
Linux
# Using Homebrewbrew install openjdk@17# Add to PATH (add to ~/.zshrc or ~/.bashrc)export JAVA_HOME=$(/usr/libexec/java_home -v 17)export PATH=$JAVA_HOME/bin:$PATH# Verifyjava -version
# Download from Microsoft# https://learn.microsoft.com/en-us/java/openjdk/download# Or using Chocolateychoco install microsoft-openjdk17# Set JAVA_HOME environment variable# System Properties → Environment Variables → New# Variable: JAVA_HOME# Value: C:\Program Files\Microsoft\jdk-17.x.x# Verifyjava -version
# Ubuntu/Debiansudo apt install openjdk-17-jdk# Fedorasudo dnf install java-17-openjdk-devel# Set JAVA_HOME (add to ~/.bashrc)export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64export PATH=$JAVA_HOME/bin:$PATH# Verifyjava -version
The Android emulator simulates a physical Android device on your computer. Unlike the iOS Simulator (which runs native ARM code directly on Apple Silicon), the Android emulator actually emulates ARM hardware, which is why hardware acceleration (Hyper-V on Windows, KVM on Linux, Hypervisor.framework on macOS) is critical for performance. Without hardware acceleration, the emulator can be 10-50x slower.A practical tip: choose a recent Pixel device image with Google Play services if you need to test Google Maps, Google Sign-In, or push notifications. System images without Google APIs will not have these services available.
# Open Android Studio# Go to: More Actions → Virtual Device Manager# Click "Create Device"# 1. Select hardware: Pixel 7 (or similar)# 2. Select system image: API 34 (download if needed)# 3. Name your AVD and finish# Or via command line# List available system imagessdkmanager --list | grep system-images# Download system imagesdkmanager "system-images;android-34;google_apis;x86_64"# Create AVDavdmanager create avd -n Pixel_7_API_34 -k "system-images;android-34;google_apis;x86_64" -d "pixel_7"# List AVDsemulator -list-avds# Start emulatoremulator -avd Pixel_7_API_34
Hardware acceleration is enabled by default on macOS with Apple Silicon or Intel HAXM.
# Enable Hyper-V (Windows 10/11 Pro)# Open PowerShell as AdministratorEnable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All# Or enable Windows Hypervisor PlatformEnable-WindowsOptionalFeature -Online -FeatureName HypervisorPlatform -All# Restart computer# For AMD processors, enable SVM in BIOS# For Intel processors, enable VT-x in BIOS
# Install KVMsudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils# Add user to kvm groupsudo adduser $USER kvm# Verify KVMkvm-ok# Logout and login for group changes
These extensions transform VS Code into a capable React Native IDE. The React Native Tools extension gives you inline debugging, and the Expo Tools extension provides autocomplete for app.json configuration (where a single typo can cause silent build failures).
# 1. Connect iPhone via USB# 2. Trust the computer on your iPhone# 3. Open Xcode → Window → Devices and Simulators# 4. Select your device and enable "Connect via network" (optional)# For development builds# 1. Sign in with Apple ID in Xcode# 2. Select your team in project settings# 3. Enable "Automatically manage signing"# Run on devicenpx react-native run-ios --device "Your iPhone Name"# Or with Exponpx expo run:ios --device
# 1. Enable Developer Options on your Android device# Settings → About Phone → Tap "Build Number" 7 times# 2. Enable USB Debugging# Settings → Developer Options → USB Debugging → Enable# 3. Connect via USB and accept the debugging prompt# 4. Verify device is connectedadb devices# Should show your device# Run on devicenpx react-native run-android# Or with Exponpx expo run:android --device
# Create local.properties in android folderecho "sdk.dir=$ANDROID_HOME" > android/local.properties# Or on Windowsecho sdk.dir=C:\\Users\\YOUR_USERNAME\\AppData\\Local\\Android\\Sdk > android\local.properties
Android: 'INSTALL_FAILED_INSUFFICIENT_STORAGE'
# Clear emulator dataemulator -avd YOUR_AVD_NAME -wipe-data# Or increase emulator storage in AVD settings
Metro bundler port in use
# Kill process on port 8081# macOS/Linuxlsof -i :8081 | grep LISTEN | awk '{print $2}' | xargs kill -9# Windowsnetstat -ano | findstr :8081taskkill /PID <PID> /F# Or use different portnpx react-native start --port 8082
# 1. Open project in Xcodeopen ios/YourProject.xcworkspace# 2. Select your project in navigator# 3. Go to Signing & Capabilities# 4. Select your Team# 5. Enable "Automatically manage signing"# 6. Change Bundle Identifier if needed
Common environment pitfalls that waste hours:iOS-specific:
Xcode updates frequently break CocoaPods caches. After any Xcode update, run pod deintegrate && pod install --repo-update in your ios/ folder.
If you see 'React/RCTBridgeModule.h' file not found, it almost always means CocoaPods failed to link properly. A clean pod install (delete Pods/ and Podfile.lock first) fixes it 95% of the time.
Running pod install with the wrong Ruby version can silently install incompatible pod versions. Use the system Ruby or rbenv to pin your Ruby version.
Android-specific:
The ANDROID_HOME environment variable must point to the SDK location, not Android Studio itself. On macOS it is ~/Library/Android/sdk, on Windows it is %LOCALAPPDATA%\Android\Sdk. Getting this wrong produces “SDK not found” errors.
Gradle daemon processes can hold stale caches. If Android builds fail mysteriously after upgrading a dependency, try cd android && ./gradlew --stop && ./gradlew clean.
The Android emulator requires significant RAM (2-4 GB per instance). If your machine has 8 GB total, expect slowdowns when running both an emulator and Metro simultaneously.
Cross-platform:
Running npm install and yarn install in the same project creates conflicting lock files (package-lock.json vs yarn.lock). Pick one package manager and stick with it.
Metro bundler defaults to port 8081. If another process (like McAfee on Windows) is using that port, Metro fails silently or with a confusing error. Use npx react-native start --port 8082 as a workaround.
A new developer on your team is on Windows and needs to develop and test iOS builds. What are their options?
Strong Answer:
They cannot run Xcode or iOS simulators on Windows — Apple restricts those to macOS. But they have three viable options. First, EAS Build runs iOS builds in the cloud on Apple silicon machines. The developer writes code on Windows, pushes to a branch, and EAS compiles the iOS build remotely.
Second, for on-device testing during development, they can use Expo Go on a physical iPhone. They run npx expo start on Windows, scan the QR code, and Expo Go loads the JS bundle over the network.
Third, for native debugging they can use cloud Mac services (MacStadium, AWS EC2 Mac instances) or pair with a macOS-using teammate for iOS-specific issues.
What I would not recommend: Hackintosh setups or assuming “it will work on iOS too” based on Android-only testing. Platform differences in keyboard handling, safe areas, and gesture navigation will surface in production.
Follow-up: The same developer reports that their Android emulator runs at 5fps on their Windows machine. How do you diagnose this?Follow-up Answer:
The number one cause is missing hardware acceleration. Check if Hyper-V or HAXM is enabled via systeminfo in PowerShell. If “Virtualization Enabled in Firmware” says No, they need to enable VT-x (Intel) or SVM (AMD) in BIOS.
Second: verify the system image architecture. Use x86_64 images on Intel hosts and arm64-v8a on ARM-based Windows. Mismatched architectures force software emulation, which is 10-50x slower.
Third: allocate at least 2GB RAM and 2 CPU cores in AVD Manager, and enable GPU acceleration (Hardware GLES 2.0).
If all else fails, use a physical Android device via USB. It is faster, shows real-world performance, and avoids emulator configuration entirely.
Explain the role of CocoaPods in React Native iOS builds. What happens when pod install fails?
Strong Answer:
CocoaPods is the dependency manager for iOS, analogous to npm for JavaScript. Running pod install reads the Podfile, resolves version constraints, downloads native dependency source code, and generates an Xcode workspace that links everything. React Native itself is a CocoaPod, along with every native module.
Debugging failures depends on the error. “CDN: trunk URL could not be downloaded”: run pod repo update. “Specs satisfying dependency were not found”: a version conflict — check Podfile.lock for pins. “No podspec found for library”: the RN library was not properly linked — ensure npx pod-install ran after npm install.
The nuclear option: cd ios && rm -rf Pods Podfile.lock build && pod cache clean --all && pod install --repo-update. This clears all cached state and rebuilds from scratch.
Critical production practice: commit Podfile.lock to version control. Without it, different team members resolve different dependency versions, causing “works on my machine” failures. Treat it like package-lock.json.
Follow-up: Your CI pipeline fails with “The sandbox is not in sync with the Podfile.lock.” What does this mean?Follow-up Answer:
The Xcode project sandbox does not match the committed Podfile.lock. Someone updated a pod locally but CI has a stale Pods/ cache.
Fix: ensure CI runs cd ios && pod install after npm ci and before the Xcode build. Do not cache Pods/ without also keying on Podfile.lock.
Preventive measure: use pod install --deployment in CI, which fails if Podfile was modified without updating Podfile.lock, catching desync issues early.
Your team uses Expo but needs to integrate a native SDK that modifies MainApplication.java. What are your options without ejecting?
Strong Answer:
First, check if an Expo config plugin already exists for the SDK. Config plugins modify native files (AndroidManifest, MainApplication, Info.plist) at build time without maintaining native code in your repository.
If no plugin exists, write a custom config plugin — a JavaScript function that receives the Expo config and returns modified native configuration. This is the modern replacement for ejecting.
If the SDK requires runtime native code, create a local Expo module using expo-modules-core. Write Swift/Kotlin that integrates with the Expo module system while keeping the managed workflow.
As a last resort, switch to development builds (npx expo run:android) which gives you the android/ directory while still using Expo’s module system. This is not ejecting — you retain EAS Build and EAS Update support.
Follow-up: The custom config plugin works locally but fails in EAS Build. How do you debug cloud build issues?Follow-up Answer:
EAS Build runs in a clean environment, so the failure likely involves environment differences. First, check the EAS Build logs for the exact error (they are available in the Expo dashboard).
Common causes: the config plugin references a local file path that does not exist in CI, or it depends on an environment variable not set in EAS secrets. Use eas build --local to reproduce the cloud build environment locally.
Add console.log statements in your config plugin — they appear in the build logs. Use the withDangerousMod modifier to inspect the generated native files mid-build and verify your modifications are applied correctly.