|
| 1 | +# Skill: Add Support for a New React Native Version |
| 2 | + |
| 3 | +This skill guides the process of adding support for a new React Native version to react-native-navigation (RNN). It covers CI setup, dependency analysis, native code fixes, playground validation, and the version-switch infrastructure. |
| 4 | + |
| 5 | +## Prerequisites |
| 6 | + |
| 7 | +Before starting, gather: |
| 8 | +- The target RN version number (e.g., `0.84.0`) |
| 9 | +- The previous highest supported RN version (check `package.json` and `.buildkite/pipeline.sh`) |
| 10 | + |
| 11 | +## Phase 1: Research Breaking Changes |
| 12 | + |
| 13 | +**This phase is critical and prevents most downstream failures. Do it thoroughly before writing any code.** |
| 14 | + |
| 15 | +### 1a. Read the RN Release Blog & Changelog |
| 16 | + |
| 17 | +Fetch the release blog post and changelog for the target version: |
| 18 | +- Blog: `https://reactnative.dev/blog` (search for the version) |
| 19 | +- Changelog: `https://github.com/facebook/react-native/blob/main/CHANGELOG.md` |
| 20 | + |
| 21 | +Focus on: |
| 22 | +- **Removed APIs/classes** (iOS and Android) -- these cause compile errors |
| 23 | +- **Changed method signatures** (Android) -- parameters becoming non-nullable, new required params, etc. (e.g., `ColorPropConverter.getColor()` changed in 0.80) |
| 24 | +- **Deprecated APIs now removed** -- check if RNN uses any deprecated APIs from prior versions |
| 25 | +- **Architecture changes** (e.g., New Architecture enforcement, legacy arch removal) |
| 26 | +- **Build system changes** -- Gradle version, Android SDK levels, Kotlin version, CocoaPods/podspec changes, Xcode requirements |
| 27 | +- **Header/import changes** -- headers moved, renamed, or removed from precompiled binaries |
| 28 | +- **CocoaPods / podspec changes** -- Check if RN changed how native modules declare dependencies (e.g., the shift to `install_modules_dependencies` in 0.80). Review `ReactNativeNavigation.podspec` for hardcoded dependency lists, header search paths, or compiler flags that may need updating. |
| 29 | +- **Kotlin version requirements** -- Newer RN versions may require newer Kotlin versions (e.g., RN 0.79+ requires Kotlin 2.x). Check if the version needs bumping. |
| 30 | + |
| 31 | +### 1b. Check RNN's Usage of Known Risk Areas |
| 32 | + |
| 33 | +Search the RNN codebase for any classes/headers mentioned as removed or changed: |
| 34 | + |
| 35 | +```bash |
| 36 | +# Search iOS for legacy RCT headers (common removal targets) |
| 37 | +grep -rn "#import <React/RCT" ios/ --include="*.h" --include="*.mm" --include="*.m" |
| 38 | + |
| 39 | +# Search Android for deprecated classes |
| 40 | +grep -rn "CSSBackgroundDrawable\|onCatalystInstanceDestroy\|ReactInstanceManager" android/ --include="*.java" --include="*.kt" |
| 41 | + |
| 42 | +# Check Android test infrastructure for classes extending RN framework classes |
| 43 | +grep -rn "extends.*React\|: React" android/ --include="*.java" --include="*.kt" | grep -i "test\|mock\|simple" |
| 44 | +``` |
| 45 | + |
| 46 | +**If the new RN version strips legacy headers from the xcframework** (look for `RCT_REMOVE_LEGACY_ARCH` changes in the RN release), identify ALL iOS files importing legacy-only headers and plan to guard them in batch. Do NOT rely on iterative build failures to find them one by one. Cross-reference each `#import <React/RCT...>` against the headers actually available in the new RN version's xcframework. |
| 47 | + |
| 48 | +### 1c. Check Third-Party Dependency Compatibility |
| 49 | + |
| 50 | +For each playground dependency that wraps native code, verify compatibility with the target RN version: |
| 51 | + |
| 52 | +1. **react-native-reanimated** -- Check https://docs.swmansion.com/react-native-reanimated/docs/guides/compatibility/ for the compatibility table. This is the most frequent source of build failures. |
| 53 | +2. **react-native-gesture-handler** -- Check release notes for RN version support. |
| 54 | +3. **react-native-fast-image** (or `@d11/react-native-fast-image`) -- Check for Fabric/new arch support. |
| 55 | +4. **react-native-worklets** -- Must be compatible with the reanimated version you chose. |
| 56 | +5. **Other native deps** -- Check each one's GitHub releases/issues for RN version support. |
| 57 | + |
| 58 | +Also check the full peer dependency tree: `npm info react-native@<version> peerDependencies` for all peer deps, not just `react`. |
| 59 | + |
| 60 | +**Key lesson from RN 0.84**: Reanimated 4.2.0 did NOT support RN 0.84 (needed 4.2.2+). The `react-native-worklets` package also needed bumping (0.7.1 -> 0.7.4). Not catching this early means the build will fail late in the process. |
| 61 | + |
| 62 | +**Key lesson from RN 0.83**: The original `react-native-fast-image` package was abandoned and had no Fabric support. The `@d11/react-native-fast-image` fork was needed as a replacement. When replacing a package, check ALL references: package.json (root AND playground), TypeScript imports, Android `build.gradle` manual `implementation project()` lines, and iOS pod references. |
| 63 | + |
| 64 | +## Phase 2: CI Infrastructure Setup |
| 65 | + |
| 66 | +### 2a. Create Buildkite Pipeline Jobs |
| 67 | + |
| 68 | +Copy an existing job file and update the version: |
| 69 | + |
| 70 | +```bash |
| 71 | +# Copy from the previous version |
| 72 | +cp .buildkite/jobs/pipeline.android_rn_83.yml .buildkite/jobs/pipeline.android_rn_84.yml |
| 73 | +cp .buildkite/jobs/pipeline.ios_rn_83.yml .buildkite/jobs/pipeline.ios_rn_84.yml |
| 74 | +``` |
| 75 | + |
| 76 | +Edit both files: |
| 77 | +- Update the label: `":android: Android (RN 0.84.0)"` |
| 78 | +- Update `REACT_NATIVE_VERSION: 0.84.0` |
| 79 | +- Update the key: `"android_rn_84"` / `"ios_rn_84"` |
| 80 | +- Keep `JAVA_HOME` on Android jobs (check if JDK version needs bumping) |
| 81 | + |
| 82 | +### 2b. Register Jobs in Pipeline Script |
| 83 | + |
| 84 | +Add the new jobs to `.buildkite/pipeline.sh`: |
| 85 | + |
| 86 | +```bash |
| 87 | +cat .buildkite/jobs/pipeline.android_rn_84.yml |
| 88 | +cat .buildkite/jobs/pipeline.ios_rn_84.yml |
| 89 | +``` |
| 90 | + |
| 91 | +Insert them in version order, before `pipeline.publish.yml`. |
| 92 | + |
| 93 | +### 2c. Optionally Drop Old Versions |
| 94 | + |
| 95 | +If the project drops support for an old version, remove its pipeline files and entries from `pipeline.sh`. RN 0.82 PR dropped RN 0.80 and 0.81 pipelines. |
| 96 | + |
| 97 | +## Phase 3: Update Default Versions and Build Configuration |
| 98 | + |
| 99 | +### 3a. Update `package.json` (Root and Playground) |
| 100 | + |
| 101 | +Update in both `/package.json` and `/playground/package.json`: |
| 102 | +- `react-native` to the target version |
| 103 | +- `react` to the matching peer dependency version (check `npm info react-native@0.84.0 peerDependencies.react`) |
| 104 | +- `react-test-renderer` to match react |
| 105 | +- `@react-native/babel-preset`, `@react-native/eslint-config`, `@react-native/metro-config`, `@react-native/typescript-config` to `0.<minor>.0` |
| 106 | +- `@react-native-community/cli`, `cli-platform-android`, `cli-platform-ios` to matching CLI version |
| 107 | +- Any third-party deps identified in Phase 1c as needing version bumps |
| 108 | + |
| 109 | +### 3b. Update Android SDK, NDK, and Kotlin Versions |
| 110 | + |
| 111 | +Check the target RN version's requirements and update in **both** `android/build.gradle` (the library defaults) and `playground/android/build.gradle`: |
| 112 | +- `compileSdkVersion` / `compileSdk` |
| 113 | +- `targetSdkVersion` / `targetSdk` |
| 114 | +- `buildToolsVersion` |
| 115 | +- `ndkVersion` |
| 116 | +- Kotlin version (`kotlinVersion` / `RNNKotlinVersion`) |
| 117 | + |
| 118 | +Cross-reference with the RN template app's `build.gradle` for the target version (`npx @react-native-community/cli init` or check the RN GitHub repo). |
| 119 | + |
| 120 | +Also update `autolink/fixtures/rn79/build.gradle.template` if SDK or Kotlin versions changed there. |
| 121 | + |
| 122 | +### 3c. Update Gradle Wrapper |
| 123 | + |
| 124 | +If the target RN requires a newer Gradle version, update `playground/android/gradle/wrapper/gradle-wrapper.properties`. |
| 125 | + |
| 126 | +### 3d. Update Version-Switch Infrastructure |
| 127 | + |
| 128 | +Update `scripts/versionMapping.js`: |
| 129 | +- Add the new minor version to `CLI_VERSION_MAP` |
| 130 | +- Update `getGradleVersion()` if the Gradle version requirement changed |
| 131 | +- Update `getReanimatedOverride()` / `shouldRemoveWorklets()` if compatibility ranges changed |
| 132 | + |
| 133 | +Update `playground/android/rninfo.gradle` if new version-conditional logic is needed: |
| 134 | +- Add new boolean flags (e.g., `isRN84OrHigher`) if Gradle build logic needs to branch on the new version |
| 135 | +- Used for things like Kotlin version branching and conditional dependency resolution |
| 136 | + |
| 137 | +### 3e. Review and Update Podspec and Podfile |
| 138 | + |
| 139 | +**`ReactNativeNavigation.podspec`** may need changes if RN altered how native modules declare dependencies: |
| 140 | +- Manual `s.dependency` lists for React submodules may be replaced by `install_modules_dependencies(s)` (happened in 0.80) |
| 141 | +- Hardcoded `HEADER_SEARCH_PATHS`, folly compiler flags, or per-arch dependency lists may become unnecessary |
| 142 | +- Compare against the new RN version's recommended podspec pattern |
| 143 | + |
| 144 | +**`playground/ios/Podfile`** may need restructuring if RN changed `use_react_native!`, new arch configuration, or post-install hooks. Compare against the RN template app's Podfile for the target version. |
| 145 | + |
| 146 | +### 3f. Install Dependencies |
| 147 | + |
| 148 | +After updating package.json and build configs: |
| 149 | + |
| 150 | +```bash |
| 151 | +cd playground && yarn install && cd ios && pod deintegrate && pod install && cd ../.. |
| 152 | +``` |
| 153 | + |
| 154 | +### 3g. Clean Stale Xcode Build Settings |
| 155 | + |
| 156 | +**Do this BEFORE the first build** -- stale settings cause mysterious "module not found" errors. |
| 157 | + |
| 158 | +The `playground/ios/playground.xcodeproj/project.pbxproj` may contain hardcoded build settings from previous RN versions (e.g., `REACT_NATIVE_MINOR_VERSION=72`, stale modulemap paths in `OTHER_CFLAGS`). Search for and remove/fix these: |
| 159 | + |
| 160 | +```bash |
| 161 | +grep -n "REACT_NATIVE_MINOR_VERSION\|REANIMATED_VERSION\|modulemap" playground/ios/playground.xcodeproj/project.pbxproj |
| 162 | +``` |
| 163 | + |
| 164 | +Replace hardcoded values with `"$(inherited)"` where appropriate. |
| 165 | + |
| 166 | +## Phase 4: Fix Native Compilation Errors |
| 167 | + |
| 168 | +### Strategy |
| 169 | + |
| 170 | +For **systematic, known patterns** identified in Phase 1 (like legacy import guarding across many files), fix all instances in batch before building. This avoids slow iterative build cycles. |
| 171 | + |
| 172 | +For **unexpected errors**, use an iterative build-fix approach: build, fix the first error, rebuild, repeat. |
| 173 | + |
| 174 | +```bash |
| 175 | +# iOS build |
| 176 | +xcodebuild build -workspace playground/ios/playground.xcworkspace \ |
| 177 | + -scheme playground -configuration Debug -sdk iphonesimulator \ |
| 178 | + -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' -quiet |
| 179 | + |
| 180 | +# Android build |
| 181 | +cd playground/android && ./gradlew assembleDebug |
| 182 | +``` |
| 183 | + |
| 184 | +### 4a. Batch-Fix: Guard Legacy iOS Imports |
| 185 | + |
| 186 | +If the new RN version removes legacy headers from precompiled binaries, guard ALL identified imports at once: |
| 187 | + |
| 188 | +1. List all files importing legacy headers: |
| 189 | + ```bash |
| 190 | + grep -rn "#import <React/RCTRootView\|#import <React/RCTRootViewDelegate\|#import <React/RCTBridge+Private\|#import <React/RCTModalHostView\|#import <React/RCTScrollView\|#import <React/RCTRootContentView" ios/ --include="*.h" --include="*.mm" |
| 191 | + ``` |
| 192 | + |
| 193 | +2. For each file, determine if the import is used only in old-arch code. If so, guard it: |
| 194 | + ```objc |
| 195 | + #ifndef RCT_NEW_ARCH_ENABLED |
| 196 | + #import <React/RCTRootView.h> |
| 197 | + #endif |
| 198 | + ``` |
| 199 | + |
| 200 | +3. **Guard the full chain**: import -> type usage -> method declarations -> protocol conformances -> method implementations. Guarding just the import without guarding the code that uses the type causes "undeclared identifier" errors. |
| 201 | + |
| 202 | +4. **Check for dead code in `#ifdef` blocks**: After guarding imports, search for old-arch symbols referenced in new-arch code paths: |
| 203 | + ```bash |
| 204 | + # Find potential dead references to bridge in new-arch blocks |
| 205 | + grep -n "getBridge\|bridgeManager\|RCTBridge" ios/ --include="*.mm" -r |
| 206 | + ``` |
| 207 | + Cross-reference hits with their surrounding `#ifdef` context. Remove any new-arch code that calls old-arch-only methods. |
| 208 | + |
| 209 | +### 4b. Common iOS Fix Patterns |
| 210 | + |
| 211 | +**Pattern 1: Guard legacy imports** -- Wrap in `#ifndef RCT_NEW_ARCH_ENABLED` / `#endif`. |
| 212 | + |
| 213 | +**Pattern 2: Guard entire files** -- If a file is only used on old arch (e.g., `RNNBridgeManager.h/.mm`), wrap the entire content in `#ifndef RCT_NEW_ARCH_ENABLED`. |
| 214 | + |
| 215 | +**Pattern 3: Runtime class lookup** -- When you need to reference a class that may not exist at compile time, use `NSClassFromString` instead of direct class references. Cache the result if it's in a hot path (like `hitTest:withEvent:`): |
| 216 | + |
| 217 | +```objc |
| 218 | +static Class myClass; |
| 219 | +static dispatch_once_t onceToken; |
| 220 | +dispatch_once(&onceToken, ^{ |
| 221 | + myClass = NSClassFromString(@"RCTModalHostView"); |
| 222 | +}); |
| 223 | +``` |
| 224 | +
|
| 225 | +**Pattern 4: Remove dead code paths** -- A `#ifdef RCT_NEW_ARCH_ENABLED` block that calls `getBridge` (old-arch only) is dead code -- remove it. |
| 226 | +
|
| 227 | +**Pattern 5: Protocol conformance guards** -- If a protocol (e.g., `RCTRootViewDelegate`) only exists on old arch, guard the conformance declaration AND the delegate method implementations: |
| 228 | +
|
| 229 | +```objc |
| 230 | +@interface MyView : UIView |
| 231 | +#ifndef RCT_NEW_ARCH_ENABLED |
| 232 | + <RCTRootViewDelegate> |
| 233 | +#endif |
| 234 | +``` |
| 235 | + |
| 236 | +### 4c. Common Android Fix Patterns |
| 237 | + |
| 238 | +**Pattern 1: Deprecated method migration** -- When RN deprecates methods with `forRemoval = true`, rename to the replacement (e.g., `onCatalystInstanceDestroy()` -> `invalidate()`). Note: only rename the RN framework override, not internal RNN methods with the same name. |
| 239 | + |
| 240 | +**Pattern 2: Removed classes** -- When Android classes are removed (e.g., `CSSBackgroundDrawable` in RN 0.83), replace with standard Android equivalents (e.g., `ColorDrawable`). |
| 241 | + |
| 242 | +**Pattern 3: Test infrastructure** -- Test/mock classes that extend RN framework classes (e.g., `SimpleView extends ReactView`) may need refactoring if the parent class behavior changed. Also check Robolectric version compatibility -- newer Android SDK levels may require bumping Robolectric, and if the new Robolectric version requires a higher Java version than CI provides, add a `robolectric.properties` file to pin the SDK level (e.g., `sdk=35` when SDK 36 requires Java 21 but CI uses Java 17). |
| 243 | + |
| 244 | +**Pattern 4: Changed method signatures** -- RN may change method signatures without deprecation (e.g., nullable parameters becoming non-nullable, or new required parameters). These cause compile errors that aren't caught by searching for removed/deprecated APIs. Check the RN changelog for API changes in utility classes like `ColorPropConverter`, `ColorParser`, etc. |
| 245 | + |
| 246 | +### 4d. Compile Error Triage |
| 247 | + |
| 248 | +When you get a compile error: |
| 249 | +1. Identify the missing symbol (header, class, method) |
| 250 | +2. Check if it's used only in old-arch or new-arch code paths |
| 251 | +3. If old-arch only: guard with `#ifndef RCT_NEW_ARCH_ENABLED` |
| 252 | +4. If new-arch only: this shouldn't happen -- investigate the API replacement |
| 253 | +5. If used in both: find the new-arch equivalent and branch with `#ifdef` |
| 254 | + |
| 255 | +### 4e. clang-format |
| 256 | + |
| 257 | +RNN uses clang-format on iOS files via a pre-commit hook. After editing `.h`/`.mm` files, verify formatting: |
| 258 | + |
| 259 | +```bash |
| 260 | +./node_modules/.bin/git-clang-format --diff -- ios/MyFile.mm |
| 261 | +``` |
| 262 | + |
| 263 | +The formatter has opinions about `#ifdef` indentation that may require unusual formatting (e.g., protocol conformance on a continuation line after `#ifndef`). |
| 264 | + |
| 265 | +## Phase 5: Validate Playground Dependencies |
| 266 | + |
| 267 | +### 5a. Check for Stale References |
| 268 | + |
| 269 | +After replacing any dependency (like fast-image -> @d11/fast-image), search for ALL references: |
| 270 | + |
| 271 | +```bash |
| 272 | +# TypeScript imports |
| 273 | +grep -r "react-native-fast-image" playground/src/ --include="*.ts" --include="*.tsx" |
| 274 | + |
| 275 | +# Android gradle -- look for manual implementation project() lines |
| 276 | +grep -r "fast-image\|fast_image" playground/android/ --include="*.gradle" |
| 277 | + |
| 278 | +# iOS podfile |
| 279 | +grep -r "fast-image\|FastImage" playground/ios/Podfile |
| 280 | +``` |
| 281 | + |
| 282 | +Note: `autolinkLibrariesWithApp()` in the playground's `build.gradle` handles most deps automatically. Manual `implementation project(':...')` lines are often stale and should be removed when the package is autolinked. |
| 283 | + |
| 284 | +## Phase 6: Full Build Verification |
| 285 | + |
| 286 | +Run both platform builds to confirm everything compiles: |
| 287 | + |
| 288 | +```bash |
| 289 | +# iOS |
| 290 | +xcodebuild build -workspace playground/ios/playground.xcworkspace \ |
| 291 | + -scheme playground -configuration Debug -sdk iphonesimulator \ |
| 292 | + -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' -quiet |
| 293 | + |
| 294 | +# Android |
| 295 | +cd playground/android && ./gradlew assembleDebug |
| 296 | + |
| 297 | +# JS tests |
| 298 | +yarn test-js |
| 299 | +``` |
| 300 | + |
| 301 | +## Phase 7: Backward Compatibility |
| 302 | + |
| 303 | +Verify that the version-switch script works for older supported versions: |
| 304 | + |
| 305 | +```bash |
| 306 | +REACT_NATIVE_VERSION=0.83.0 node scripts/changeReactNativeVersion.js |
| 307 | +# Then rebuild to verify |
| 308 | +``` |
| 309 | + |
| 310 | +## Phase 8: Documentation |
| 311 | + |
| 312 | +Update `website/docs/docs/docs-Installing.mdx`: |
| 313 | +- Update the supported RN version range |
| 314 | +- Note any breaking changes users need to be aware of |
| 315 | + |
| 316 | +## Common Pitfalls (Lessons Learned) |
| 317 | + |
| 318 | +1. **Don't assume deps are compatible** -- Always check third-party native dep compatibility tables BEFORE starting the build. Reanimated is the most common offender. |
| 319 | + |
| 320 | +2. **Search for ALL references when replacing a package** -- TypeScript imports, Android gradle `implementation project()` lines, iOS pod references, root AND playground package.json. Missing one causes CI failures. |
| 321 | + |
| 322 | +3. **Legacy headers in precompiled binaries** -- When RN enforces new arch by default (like 0.84 with `RCT_REMOVE_LEGACY_ARCH=1`), headers for old-arch classes are stripped from the xcframework. Any unguarded `#import` of these headers causes a compile error. Batch-fix all legacy imports identified in Phase 1b rather than finding them one build at a time. |
| 323 | + |
| 324 | +4. **Guard code, not just imports** -- Guarding an import without guarding the code that uses the imported types leads to "undeclared identifier" errors. Always guard the full chain: import -> type usage -> method declarations -> method implementations. |
| 325 | + |
| 326 | +5. **Dead code in `#ifdef` blocks** -- When splitting code paths between old/new arch, check that each path only references symbols available in that architecture. A common bug is new-arch code calling old-arch-only methods (like `getBridge` in a `#ifdef RCT_NEW_ARCH_ENABLED` block). |
| 327 | + |
| 328 | +6. **Stale Xcode project settings** -- The `.pbxproj` file can accumulate hardcoded build flags from old versions. These cause mysterious "module not found" errors. Check for stale `OTHER_CFLAGS` BEFORE the first build (Phase 3g). |
| 329 | + |
| 330 | +7. **clang-format surprises** -- The formatter enforces specific indentation around preprocessor directives. Run the check before committing to avoid hook failures. |
| 331 | + |
| 332 | +8. **Batch-fix systematic patterns, iterate on surprises** -- When you identify a pattern that affects many files (like legacy import guarding), fix all instances at once. Use iterative build-fix only for unexpected or one-off errors. |
| 333 | + |
| 334 | +9. **Android SDK level cascading effects** -- Bumping `compileSdkVersion`/`targetSdkVersion` can require bumping Robolectric, which may require a higher Java version than CI provides. Solution: pin the Robolectric SDK via `robolectric.properties` (e.g., `sdk=35` when SDK 36 needs Java 21). |
| 335 | + |
| 336 | +10. **Podspec can be a major effort** -- Don't underestimate podspec changes. RN 0.80 required replacing the entire dependency declaration strategy. Compare your podspec against the RN template's pattern for the target version. |
0 commit comments