On React Native 0.88.1 with TypeScript 7.0.2, ten typical react-native/Libraries/* deep imports gave 10 type errors under the default config, 5 with the react-native-legacy-deep-imports opt-out, and 0 once rewritten to imports from react-native itself. The takeaway: the opt-out is not a free pass. It brings back the old .d.ts files, which only have named exports, so the default imports most older code uses still fail. Rewrite to root exports now; nine of our ten had a direct equivalent.
We tested this on 6 Nov 2026 in a Linux container (Ubuntu 24.04, Node 22.23.4) against a fresh install of react-native@0.88.1, @react-native/typescript-config@0.88.1, typescript@7.0.2 and Jest 30.5.2.
Why we did this: our Beacon-type banking apps started on React Native 0.76, and native-module wrappers written in that era reach into react-native/Libraries for event emitters, Platform and codegen helpers. The Strict TypeScript API has been the default since 0.87, and the React Native docs say the opt-out will be removed in a future release without naming one. We wanted to know how much of the opt-out actually helps before deciding whether to lean on it.
What changed in the package
The answer is in node_modules/react-native/package.json. In 0.88.1 the ./Libraries/* export has "types": null, so TypeScript finds no types for any deep path. Only the custom condition maps it to something:
"./Libraries/*": {
"types": null,
"default": "./Libraries/*.js",
"react-native-legacy-deep-imports": [
"./types_DEPRECATED/Libraries/*.d.ts",
"./Libraries/*.d.ts"
]
}
The runtime path is untouched ("default": "./Libraries/*.js"), so the app still bundles and runs. This is purely a type-checking change, which is why it shows up in CI rather than on a device.
The test: ten deep imports, four configs
We wrote one module with ten deep imports of the kinds that turn up in 0.7x-era code: NativeEventEmitter, RCTDeviceEventEmitter, Platform, codegenNativeComponent, TurboModuleRegistry, the TurboModule type, ViewProps, TextInputProps, processColor and resolveAssetSource. Then we ran tsc --noEmit against it with four configs, all extending @react-native/typescript-config.

Error counts from tsc --noEmit, run in the container on 6 Nov 2026.
- Default config (strict): 10 errors, all
TS2307 Cannot find module. Every deep import fails, as expected. - Legacy condition, imports as written: 5 errors. The other five pass, including the type-only imports.
NativeEventEmitter,RCTDeviceEventEmitter,PlatformandprocessColorfail withTS2351,TS2339andTS2349, because the deprecated.d.tsfiles export them by name (export const Platform,export function processColor) while the code imports a default export, which is what the JS files provide at runtime. - Legacy condition, switched to named imports: 1 error.
react-native/Libraries/Image/resolveAssetSourcehas a JS file but no.d.tsin either location, so it never had types on this path. - Root imports, default config: 0 errors.
The legacy condition in our tsconfig looked like this:
{
"extends": "@react-native/typescript-config",
"compilerOptions": {
"customConditions": ["react-native", "react-native-legacy-deep-imports"]
}
}
So if your wrappers use default imports from deep paths, turning on the opt-out still leaves you editing every one of those lines. At that point you might as well point them at the root export.
The rewrite
Nine of the ten have a root export in 0.88.1: we checked each name against types_generated/index.d.ts. RCTDeviceEventEmitter becomes DeviceEventEmitter. The tenth, resolveAssetSource, is not exported on its own, but Image.resolveAssetSource covers the same call.

The rewritten module type-checks with the default strict config.
One error came up after the rewrite that the deep imports had hidden: our test Spec for a TurboModule was passed to new NativeEventEmitter(...), and the strict types require the module to declare addListener(eventName: string): void and removeListeners(count: number): void. Older native-module wrappers often skip those two methods in the spec. Adding them fixed it and matches what the native side has to implement anyway.
Jest setup: InitializeCore and setup-env
The docs say react-native/Libraries/Core/InitializeCore is deprecated since 0.87 and should be replaced with react-native/setup-env. We checked both:
# jest.config.js: preset '@react-native/jest-preset', setupFiles: ['<rootDir>/tests/setup-old.js']
npx jest -c jest.old.config.js # require('react-native/Libraries/Core/InitializeCore')
npx jest -c jest.new.config.js # require('react-native/setup-env')
Both passed the same smoke test (timers, requestAnimationFrame, Platform.OS, NativeEventEmitter) in Jest 30.5.2. The difference only shows in a TypeScript setup file: import 'react-native/Libraries/Core/InitializeCore' fails with TS2882 under the default config, and import 'react-native/setup-env' passes. The 0.88.1 Jest preset already maps ^react-native/setup-env$ to src/setup-env.js, so no extra moduleNameMapper is needed.
What about third-party libraries?
We installed nine libraries that a banking app of ours typically carries and grepped their shipped code for react-native/Libraries/:
| Library | Version | Files with deep imports |
|---|---|---|
| react-native-safe-area-context | 5.11.1 | 10 (codegen specs) |
| react-native-gesture-handler | 3.5.0 | 8 |
| react-native-screens | 4.30.1 | 3 |
| react-native-keychain, react-native-biometrics, async-storage, device-info, react-native-svg, @react-navigation/native | latest | 0 |
None of them broke tsc for the app: a test file importing seven of them (safe-area-context, screens, gesture-handler, keychain, async-storage, device-info, React Navigation) gave 0 errors, because their types resolve to .d.ts files under lib/typescript and skipLibCheck is on in the React Native preset. With --skipLibCheck false the same file gave 9 errors, one of them an unresolved deep import inside safe-area-context’s NativeSafeAreaView.d.ts. Library deep imports are the maintainers’ problem, not a blocker for your upgrade, as long as you keep skipLibCheck on.
Finding your own count
This one-liner lists every deep import in app code, tests and patches. Run it from the project root:
grep -rnoE "(from|require\()\s*['\"]react-native/Libraries/[^'\"]+" \
src tests patches jest.setup.* 2>/dev/null \
| sed -E "s/.*react-native\/Libraries\///" | sort | uniq -c | sort -rn
On our test tree it found 21 lines: two copies of the ten imports plus the old Jest setup import.
Our decision
We will not rely on react-native-legacy-deep-imports. In our test it fixed half the errors, the docs say it is going away, and the remaining fixes are the same edits as a full rewrite. For the banking apps the plan is the grep above, a rewrite to root exports, the two missing methods in each TurboModule spec, and react-native/setup-env in Jest setup, done before the 0.89 upgrade rather than during it.



