refactor: rn 0.87 compatibility - #3786
Merged
Merged
Conversation
Contributor
SDK Size
|
oliverlaz
approved these changes
Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 Goal
Support React Native 0.87.
0.87 makes the Strict TypeScript API the default and drops several types from the root
react-nativeexport, which breaks our type-check. Its Babel preset also miscompiles one of our shipped files.The SDK now builds, type-checks and runs on both 0.86 and 0.87. Peer range (
>= 0.76.0) naturally remains unchanged, no public API removed.🛠 Implementation details
Strict TypeScript API - new
src/types/react-native-compat.tsexportingViewRef/TextInputRef/ScrollViewRef/FlatListRefetc. asReact.ComponentRef<typeof X>. The "fix" is in the semantics, as it resolves to the old class-instance types on<= 0.86and the new ones on0.87, so we keep the0.76floor. Using RN's own*Instancenames would have dropped everything below0.87. Same file redeclaresKeyboardEventListener,ViewTokenandViewabilityConfig, which 0.87 no longer exports.API shape changes -
useColorScheme()andfindNodeHandle()have becomenullable,AppState.currentStatewidened tostring | null | undefined,FlatList'sdataandListFooterComponenthave stopped acceptingnull, style objects have becomeReadonly,NativeEventEmitterhas become generic.onAccessibilityActionmoved toViewProps.Type leaks - explicit annotations on
Input,useAnimatedGalleryStyle,SafeAreaViewWrapperandImageGalleryFooter, plus aThemeStyleunion, to stop RN-internal names leaking intolib/typescript.SqliteClientruntime fix - statics were arrow class fields callingthis.foo().0.87's Babel preset hoists that to a module-levelvar _this = this, solib/commonjsshipped calls against the wrong object and threw. Now references the class explicitly.Build config - both wrappers'
tsconfig.jsonopt into legacy deep-import types (the codegen spec must keep the deepcodegenNativeComponentimport; the root export only exists from 0.80). Android: Java 1.8 => 17, Kotlin 1.7 => 2.2,minSdk21 => 24,compileSdkfallback 31 => 34.compileSdkis 34 rather than 37 on purpose, as0.87'sreact-androiddeclaresminCompileSdk=34and a library'scompileSdkcaps what we may call, it isn't a floor on integrators. It's only reached when the app doesn't setrootProject.ext.compileSdkVersion, which basically any app always does.Jest - two
moduleNameMappershims, both third-party:For our integration tests to work I had to do some mental gymnastics to get everything off the ground.
react-native-svg(<=15.15.5) readsTouchable.Mixinoff the RN root.0.87keeps that export alive for exactly this call site but installs it late and non-enumerably, so Babel's ESM interop drops it under Jest.4.6.0'sinitializeReanimatedModule()callssetCSSEventHandler()unconditionally, which throws on the JS only backend Jest uses.moduleNameMapperrather thanjest.mock('react-native', …)because it's resolver-level, so it also covers the separate registry Jest builds for automocks and it doesn't shadow per suite RN mocks.Example apps - SampleApp gets the AGP 9 proguard fix (
proguard-android-optimize.txt; AGP 9 rejectsproguard-android.txtoutright and since it's evaluated at configuration time it breaks the debug build too), AGP 9 opt-outs, SDK 37/36, and 13 strict-API fixes in its own code. ExpoMessaging deliberately stays on Expo 57 / RN 0.86 for now, while Expo58remains canary only and holding it back doubles as backwards-compat coverage for now. Once 58 ships we can likely bump that too pretty easily.Native source - no changes. None of
0.87's removed native APIs are used, headers already use the namespaced<React/...>form, andStreamShimmerViewComponentViewalready sets_props = defaultPropsininitWithFrame— the thing0.87now asserts on.🎨 UI Changes
🧪 Testing
☑️ Checklist
developbranch