Skip to content

feat: support React Native 0.87.1 and fix navigation edge cases - #8344

Open
OskarEichler wants to merge 3 commits into
wix:masterfrom
OskarEichler:codex/react-native-087-compatibility
Open

feat: support React Native 0.87.1 and fix navigation edge cases#8344
OskarEichler wants to merge 3 commits into
wix:masterfrom
OskarEichler:codex/react-native-087-compatibility

Conversation

@OskarEichler

@OskarEichler OskarEichler commented Aug 27, 2026

Copy link
Copy Markdown

Review fixes included

This PR now includes the follow-up correctness and performance fixes found during the full branch review, in addition to RN 0.87.1 compatibility:

  • Processors and events: remove only the exact processor registration (including duplicate callbacks and repeated unsubscribe); honor transformed option values before native color/image normalization; route native screenPopped events correctly; check deprecated option paths once per options object.
  • Deep links: ignore stale subscriptions, consume the initial URL once, preserve a readiness-queued launch URL across reconfiguration, and handle initial-URL/modal-presentation promise failures.
  • Android animation lifecycle: replace GlobalScope preparation with controller-owned cancellable work; remove render/image listeners on cancellation or teardown; cancel pending animations as well as started ones. Shared-element image readiness accepts downscaled images and has a one-second fallback per image instead of waiting indefinitely.
  • Custom bottom tabs: attach/detach directly with the tab view, remove activity-wide recursive view-tree scans, and clean up overlay/config listeners when tabs disappear. Include regression coverage for detach/reattach and modal dismissal/reopening.
  • Modal sizing: use declarative, window-responsive dimensions and slide distances; prevent Android prop updates from presenting the same modal twice. The exported class and existing props remain intact.
  • iOS animation/layout: call the original tab-button layout implementation with the correct Objective-C arguments, replace only the button class's implementation, use monotonic display-link timing, and avoid skipping animators when an earlier one finishes.
  • Dependencies and test tooling: remove RNN's direct legacy lifecycle-polyfill usage, restore Gesture Handler 2 when switching back to RN 0.85, repair snapshot build commands and simulator selection, serialize shared iOS build output, and keep snapshot recording local without automatic Git changes or publication.

Regression tests cover these fixes. Compatibility and externally observable behavior changes are called out below; reference-device visual limitations remain explicit.

Most findings fix pre-existing library behavior, not regressions introduced by RN 0.87. The Gesture Handler round-trip issue was introduced by the compatibility mapping. The follow-up is kept in a separate commit for review. Performance improvements remove repeated work; no frame-time benchmark is claimed.

Summary

Add React Native 0.87.1 compatibility while keeping the repository's RN 0.85.2 development baseline and existing public navigation API.

  • iOS: guard removed legacy bootstrap APIs, remove obsolete imports, and use canonical RCTHost imports that work with both source and prebuilt React Native.
  • Android: adapt Kotlin/Fresco accessors and modern ReactHost dev-support access. Replace removed ViewGroupManager.getViewZIndex with Fabric native-hierarchy ordering, preserving sibling order when shared elements are restored.
  • TypeScript: support RN's strict public API/package exports without legacy deep-import opt-ins; preserve native codegen output.
  • Autolinking: support the RN 0.87 Kotlin template without introducing a legacy host or initializing React Native twice.
  • CI/playground: add RN 0.87.1 jobs and toolchain/dependency mappings; preserve the Gesture Handler root wrapper across GH 2/3; fix native test-host duplicate linkage, UIKit mock fixtures, and asynchronous E2E cleanup/assertions exposed during verification.

Suggested release line: RNN 8.9.0. No package version bump is included; final release numbering is left to maintainers.

Breaking changes and migration notes

React Native Navigation API

No public navigation API removals, signature changes, or native codegen contract changes are intended. Correctness fixes do change the observable behaviors listed below. The existing RN 0.85.2 development baseline is retained; consumers are not forced to upgrade React Native by this PR. Declaration compatibility is also checked against RN 0.77.

Android shared-element stacking now follows Fabric's native sibling/ancestor ordering and Android elevation, replacing the removed RN z-index helper. Apps relying on the old incorrect stacking order may see corrected overlap during transitions. Original child order is restored after completion/cancellation.

Observable behavior changes from the review fixes

  • A pop now invokes component screenPopped listeners, not preview listeners. Unsubscribing a processor no longer removes later registrations; repeated removal is a no-op. Custom color/image transformations now reach native normalization. Multiple processors retain the existing original-input/last-result-wins behavior.
  • Reconfiguring linking no longer replays a consumed launch URL. A launch URL still awaiting readiness uses the latest configuration. Later live URL events are not deduplicated, even for the same URL. Initial-URL lookup and automatic modal-presentation failures produce warnings instead of unhandled promise rejections.
  • Android shared-element preparation stops waiting for an image after one second per image and falls back without that element; cancelled/destroyed screens cannot resume stale preparation. Apps depending on an indefinite wait for a slow image should preload it.
  • Declarative modal dimensions and slide distances follow window-size changes. Updating modal props no longer triggers a second native presentation.
  • Snapshot recording no longer changes Git configuration, creates a remote, commits, or pushes. Contributors/CI must explicitly review and publish recorded images; build/test failures remain nonzero.
  • react-lifecycles-compat is no longer a direct RNN/playground dependency or invoked by RNN. It remains a transitive playground dependency through remx; this does not raise the supported React Native floor.

Requirements when upgrading an app to RN 0.87.1

  • Adopt RN 0.87's Android toolchain: Node >=22.13.0, SDK/Build Tools 37, Kotlin 2.0+ (template 2.2.0), AGP 9, and the template Gradle wrapper (9.4.1). Keep android.builtInKotlin=false and android.newDsl=false while using this integration.
  • RN's strict public TypeScript API is now the default. Use an exports-aware resolver (moduleResolution: "bundler" / module: "esnext", or the RN TypeScript configuration), migrate private RN deep imports and obsolete ref types in application code. RNN no longer requires the legacy deep-import opt-out; its Color type uses RN's public ColorValue.
  • For the new Kotlin template, retain reactHost, extend NavigationApplication, and remove loadReactNative(this) because RNN initializes RN. Existing legacy-host applications remain supported; use NavigationReactNativeHost(this@MainApplication) for the anonymous host.
  • Keep CocoaPods for this RNN integration. Both source and prebuilt RN are verified; RN's experimental Swift Package Manager path is not implemented or validated by this PR. Reinstall Pods after upgrading.

The RN upgrade also removes/deprecates APIs outside RNN. Consult the official RN 0.87 breaking-change notes when migrating application code.

Contributor/playground changes (not consumer requirements)

  • RN 0.87 CI selects Gesture Handler 3.2.1, Reanimated 4.6.0, and Worklets 0.12.1. GH 3 removes gestureHandlerRootHOC; the playground uses a compatible GestureHandlerRootView wrapper that preserves screen statics on GH 2 and 3. This does not impose these dependencies on RNN consumers.
  • The playground no longer overrides global alert; its explicit showAlert helper keeps the same overlay behavior.
  • Native test targets inherit host-app Pod search paths rather than relinking RN/RNN. Android unit CI runs the aggregate library test task because AGP 9 no longer provides the old release unit-test task by default. CI now fails on a version-switch error and builds the library declarations for each RN version.

Verification after the review fixes

On RN 0.87.1 and RN 0.85.2:

  • Library JS/declaration builds passed; Jest: 418 passed, 60 existing skips, four snapshots on each version.
  • Android library unit tests: 723 passed, two existing skips on RN 0.87; RN 0.85 Debug and Release each pass the same counts.
  • iOS native unit tests: 360 passed on each version (RN 0.87 prebuilt and RN 0.85 source); corresponding Release builds passed.
  • Android lint: zero errors, 82 warnings on each version; XML inspected because upstream uses abortOnError false.
  • Full ESLint: zero errors (133 warnings on RN 0.87 / 120 on RN 0.85). All changed JS/TS files, including normally ignored scripts/E2E files, pass targeted lint with zero warnings.
  • RN 0.85 immutable dependency install passed. RN 0.77/0.85 projected declaration checks passed.

Additional RN 0.87 checks:

  • Android connected instrumentation: 59 passed; Release app/test APK builds and codegen generation passed.
  • iOS prebuilt focused E2E: 35 passed, five platform skips, covering stack navigation, deep linking, and custom tabs. The final class-local tab-button method replacement was subsequently rebuilt and covered by the 360 native tests.
  • Android focused E2E: 57 passed, two platform skips, one intermittent failure across stack navigation, custom tabs, deep links, modals, and shared elements. All nine custom-tab checks, all 20 modal checks, and repeated shared-element push/pop passed. The remaining rapid pop/re-push test passed an unchanged isolated rerun; the same failure was reproduced on the pre-fix runtime (see limits below).
  • Autolink: five passed, two existing skips, four snapshots.
  • React Doctor changed-file scan: no diagnostics (50 files scanned; score 66/100). It helped identify the modal-sizing modernization. git diff --check also passed.
  • Snapshot-runner command selection/no-publication behavior and RN 85 → 87 → 85 dependency switching have regression tests. Full visual-reference suites were not regenerated or declared green by this follow-up.
Earlier compatibility verification (before the follow-up fixes)

RN 0.87.1, Node 22.23.2, Java 17, Xcode 26.6 / iOS Simulator 26.5, Android API 35 emulator:

  • Library JS/declaration build: passed.
  • Jest: 402 passed, 60 skipped, four snapshots passed.
  • Autolink suite: five passed, two existing skips; RN 0.87 Kotlin template covered, plus a manual Swift-template transform check.
  • Android library unit tests: 714 passed, two skipped, including reload broadcasts/listener teardown on API 28, 33, and 34.
  • Android connected instrumentation tests: 59 passed.
  • Android Release app and instrumentation APK builds: passed.
  • iOS Release source and prebuilt app builds: passed.
  • iOS native unit tests: 357 passed with source React Native, 357 passed with prebuilt React Native. Five previously crashing modal tests also passed three repetitions.
  • iOS source E2E: all 20 suites passed across the full run and targeted reruns (173 passed, 26 existing/platform skips). Initial orientation-cleanup and asynchronous deep-link assertion failures were fixed; both affected suites passed all 12 tests on rerun.
  • Android full serial E2E: 158 passed, 38 skipped; four bitmap comparisons initially failed on image dimensions. A final RN 0.87 ReactHost-only Release build and targeted device rerun passed 11 checks, including the button bitmap, shared elements, events, alerts, and declared modals; three device-dependent bitmap comparisons remain below threshold (details below).
  • iOS prebuilt E2E: 31 passed, three platform/existing skips across stack navigation, deep linking, and orientation suites.
  • Final iOS prebuilt E2E rerun after the lint/review changes: nine passed covering alert overlays, native events, custom buttons/unmounting, and declared modals.
  • RN 0.85.2 backward check: library build and Jest pass (402 passed, 60 skipped, four snapshots); Android Debug and Release units each pass 714 tests with two skips. iOS native units: 357 passed.
  • Declaration checks against RN 0.77 and 0.85 types, and codegen schema parity with RN 0.77/0.87 parsers: passed.
  • Changed-file ESLint and git diff --check: passed (four existing script style warnings).

Existing/environmental verification limits

  • Pre-existing Android rapid pop/re-push race: the unchanged UnmountRaceScreen fixture fires pop and push concurrently and clears module-global data on unmount. The new screen can display stale_unmount when the outgoing screen unmounts after the new screen mounts. The follow-up build failed once and passed an unchanged isolated rerun. For a controlled comparison, restored the Android/JS runtime files to pre-fix commit b58d499, rebuilt the RN 0.87 Release app, and ran the same test: three passes followed by the identical failure on attempt four. Restored all fixes afterward. This is not a demonstrated regression from these fixes, but the fixture/overlapping-command lifecycle behavior remains unresolved; the Android E2E suite is not claimed fully green. The assertion was not weakened and the test was not skipped.
  • The original compatibility pass fixed five pre-existing playground lint errors; the latest lint results are listed above.
  • The original compatibility pass fixed 14 pre-existing Android lint errors without disabling rules; the latest reports have zero errors and 82 warnings.
  • Android bitmap references target a Pixel 3a. Matching the available Pixel 8 Pro emulator to 1080x2220 / 440 dpi fixes the button bitmap check, but its camera cutout still reserves a 112px status-bar inset versus 66px in the references. Two bottom-tabs captures and the banner-overlay capture remain below the 0.99 SSIM threshold (0.563 / 0.915 / 0.673); inspected captures show the corresponding vertical layout offset. These three visual checks need rerunning on the reference AVD before claiming a completely green snapshot suite. References were not regenerated or thresholds weakened.
  • The iOS snapshot runner is now repaired and accepts simulator model/OS overrides. Reference images still require their original model/OS: the earlier manual SnapshotTests runs on iPhone 17 Pro Max / iOS 26.5 differed from all 18 checked-in references, while all 18 RN 0.87 captures were pixel-identical to RN 0.85.2 on that same simulator. That parity result predates the follow-up fixes; no new full visual parity claim is made here, and no references were regenerated.
  • The playground-wide TypeScript project has existing legacy Reanimated demo errors; the published library declarations build successfully.

Adapt native APIs, Fabric shared-element ordering, strict public types and modern ReactHost autolinking. Add compatibility CI and regression coverage, repair native test-host linkage, and clear existing lint errors.
Address the branch review findings with processor/event/linking fixes, cancellable Android preparation, direct custom-tab lifecycle tracking, responsive modal sizing, safer iOS animation/layout handling, and reproducible contributor tooling. Add regression coverage and document observable behavior changes.
@OskarEichler OskarEichler changed the title feat: support React Native 0.87.1 feat: support React Native 0.87.1 and fix navigation edge cases Aug 27, 2026
@OskarEichler

Copy link
Copy Markdown
Author

@mobileoss 🙌🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant