← Back to blogReact Native

Setting up React Native in 2022: what nobody tells you

January 18, 2022·6 min read·2 comments

The React Native getting-started guide is fine. It walks you through installing dependencies, initializing a project, and running it on a simulator. You see "Welcome to React Native" on your screen and feel productive. Then you try to do anything else, and everything falls apart.

I've set up React Native projects from scratch on at least a dozen different machines over the past few years. Every single time, something goes wrong that the docs don't mention. This isn't a complaint about the docs being bad. They cover what they need to cover. The problem is that the environment around React Native, the JDKs, the build tools, the native package managers, changes faster than any documentation can track.

JDK version mismatches

React Native requires a specific JDK version. As of 2022, that's JDK 11 for most projects, though some configurations want JDK 17. The issue is that your machine probably has a different version installed, or multiple versions, and nothing tells you which one Gradle is actually using.

The symptom is a build failure with an error like "Unsupported class file major version 61" or "Could not determine java version from '17.0.1'". Neither of these errors mentions the JDK or tells you what to do.

The fix: check what Gradle is using.

./gradlew --version

This prints the JDK version Gradle picked up. If it's wrong, set JAVA_HOME explicitly:

export JAVA_HOME=$(/usr/libexec/java_home -v 11)

On Linux, the path differs. The important thing is that JAVA_HOME must point to the correct JDK root, not just any Java binary on your PATH. Add this to your shell profile so you don't have to set it every session.

If you have multiple JDKs installed via Homebrew, jenv can manage the active version per directory. But I've found that setting JAVA_HOME directly is more reliable because Gradle doesn't always respect jenv's selection.

Gradle daemon issues

The Gradle daemon runs in the background to speed up builds. It also caches things aggressively, and those caches go stale. When they do, you get build failures that make no sense.

The first thing to try is killing the daemon:

cd android && ./gradlew --stop

If that doesn't fix it, clear the Gradle caches:

rm -rf ~/.gradle/caches/
rm -rf android/.gradle/
rm -rf android/build/
rm -rf android/app/build/

Then rebuild. This fixes about 70% of mysterious Android build failures.

On M1 Macs, there's an additional issue. The Gradle daemon sometimes starts as an x86 process under Rosetta, even if you have an ARM JDK installed. This causes subtle performance problems and occasional crashes. The fix is to make sure your terminal is running natively (not under Rosetta) and that the ARM JDK is selected.

CocoaPods on M1 Macs

CocoaPods is Ruby-based. M1 Macs ship with a system Ruby that doesn't play well with CocoaPods. The symptom is an error during pod install about incompatible architectures or missing gems.

The fix that has worked for me consistently:

brew install ruby
echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
sudo gem install cocoapods

Then, in your iOS directory:

cd ios && pod install --repo-update

If you still get architecture errors, prefix with arch -arm64:

arch -arm64 pod install

Some guides suggest using ffi workarounds. In my experience, installing CocoaPods on a Homebrew Ruby avoids the ffi issue entirely.

Metro bundler cache

Metro caches transformed JavaScript bundles. When you update dependencies or change configuration, the cache can serve stale bundles. The symptom is usually a runtime error that doesn't match your source code, or a module not found error for a package you just installed.

Clear it:

npx react-native start --reset-cache

Or manually:

rm -rf $TMPDIR/metro-*
rm -rf $TMPDIR/haste-map-*

I run the cache clear on every dependency change. It adds a few seconds to startup and prevents an entire category of debugging sessions.

The JAVA_HOME path

This deserves its own section because it causes more wasted hours than everything else combined. The issue is that JAVA_HOME must be set before the Android build toolchain runs, and different tools read it from different places.

If you use zsh (the default on modern macOS), add it to ~/.zshrc. If you use bash, add it to ~/.bash_profile. If you launch your editor from Spotlight or the Dock, it might not inherit your shell environment at all.

For VS Code and Cursor, the terminal inherits the shell profile. For Android Studio, it reads JAVA_HOME from its own settings. These can be different, and when they are, builds succeed in one but fail in the other.

The most reliable setup I've found: set JAVA_HOME in ~/.zshrc, verify it with echo $JAVA_HOME in every terminal you use, and set it again in Android Studio under Preferences, Build, Execution, Deployment, Build Tools, Gradle, Gradle JDK.

The full clean rebuild

When nothing else works, this is the nuclear option:

watchman watch-del-all
rm -rf node_modules
rm -rf $TMPDIR/metro-*
rm -rf $TMPDIR/haste-map-*
npm install
cd ios && pod install --repo-update && cd ..
cd android && ./gradlew clean && cd ..
npx react-native start --reset-cache

I have this saved as a shell script called nuke.sh in every React Native project. I run it about once a week.

None of this is hard once you know it. The problem is that you only learn it by hitting the wall, because the documentation assumes a clean environment that doesn't exist on any machine that has been used for development for more than a month. Hopefully this saves you the four days I lost the first time.

RESPONSES
Alex RiveraFeb 3, 2022

This saved me two days of pain last week. The Gradle daemon section specifically — I had been googling this for hours and none of the Stack Overflow answers addressed the M1 issue properly. Bookmarked.

James ChenFeb 11, 2022

The CocoaPods fix worked. I was about to give up and go back to Expo. Thanks for this.

Leave a response