Skip to content

Commit 27aa131

Browse files
yedidyakclaude
andcommitted
Add skill document for RN version upgrade process
Formalises the process of adding new React Native version support, validated against PRs for RN 0.80 through 0.84. Covers CI setup, dependency analysis, native compilation fixes, and 10 common pitfalls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7824639 commit 27aa131

File tree

1 file changed

+336
-0
lines changed

1 file changed

+336
-0
lines changed
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
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

Comments
 (0)