On This Page
1. The Error
There are two wordings of this error, and which one you see depends on your architecture.
Fabric (New Architecture, RN 0.82+, mandatory since 0.82) — the app doesn't crash outright. Fabric renders a placeholder in place of the missing view and logs a warning:
ERROR Unimplemented component: <RNSScreen>
WARN ViewConfig for component RNSScreen not found. This is a critical error
and it might crash soon. This can happen if the native component is not
registered correctly via ComponentDescriptorProvider.On screen you get a solid gray or pink box with the component name printed on it, sitting where your <Screen>, <SafeAreaProvider>, or custom native view should be. When the missing view is load-bearing for layout — most commonly a native-stack header component from react-native-screens — the missing measurements can cascade into a genuine crash a few frames later instead of a quiet placeholder:
*** Terminating app due to uncaught exception 'RCTFatalException',
reason: 'Unhandled JS Exception: Invariant Violation: View config not found
for component RNSScreenStackHeaderConfig'Legacy architecture (RN ≤ 0.81, or New Architecture explicitly disabled where that's still possible) — this is a hard, synchronous crash at the point requireNativeComponent resolves the view name against the bridge's UIManager:
Invariant Violation: requireNativeComponent: "RNSScreen" was not found in the
UIManager. This error is often caused by:
* Native code is not linked, or Metro needs to be restarted.
* Custom navigator does not implement onLayout.
* You have two copies of react-native installed.
This error is located at:
in RNSScreen
in Unknown
in Screen
in ScreenStack
in NativeStackView
in NativeStackNavigator
at requireNativeComponent (UIManager.js:1)
at ScreenStack (NativeStackView.tsx:1)
at renderApplication (renderApplication.js:1)
at AppRegistry.runApplication (AppRegistry.js:1)Both messages are RN-internal Invariant Violation / warning strings, built from a template literal at throw time — not a minified React error code — so the wording itself survives a release build unchanged. What you lose in release is symbolication above it: without an uploaded Hermes source map, everything below the requireNativeComponent frame collapses to <unknown> at hex offsets instead of your component names.
This article uses RNSScreen / RNSScreenStackHeaderConfig (from react-native-screens, pulled in by @react-navigation/native-stack and Expo Router) as the running example, because it's the single most frequently reported instance of this failure across react-native-screens, react-native-safe-area-context, react-native-maps, react-native-blur, and any other library that ships its own native view manager. The mechanism and the fix are identical for any of them — only the component name in the message changes.
This is a native view manager, not a TurboModule method. If your error instead reads TurboModuleRegistry.getEnforcing(...): 'X' could not be found, that's a different failure — a JS method call into a native module, not a <View> — covered separately.
2. How to Reproduce It (step-by-step)
The fastest real repro is the one that actually bites teams: add a library with a native view manager, then run the JS bundle against a native app you didn't rebuild.
bash
npx @react-native-community/cli@latest init Repro --version 0.87.0
cd Repro
npm install @react-navigation/native @react-navigation/native-stack \
react-native-screens react-native-safe-area-contextpackage.json dependency block:
json
{
"dependencies": {
"react": "19.2.0",
"react-native": "0.87.0",
"@react-navigation/native": "^7.1.0",
"@react-navigation/native-stack": "^7.3.0",
"react-native-screens": "^4.15.0",
"react-native-safe-area-context": "^5.6.0"
}
}App.tsx:
tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Text, View } from 'react-native';
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home</Text>
</View>
);
}
const Stack = createNativeStackNavigator();
export default function App() {
return (
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
}Now reproduce the exact failure mode teams hit in practice — install the JS packages, then start Metro without doing the matching native install step:
bash
npm install
npx react-native start --reset-cache &
npx react-native run-ios # skipped: cd ios && pod installOn iOS this throws immediately because the new pods (RNScreens, RNCSafeAreaContext) were never added to the Xcode project, so their ComponentDescriptorProvider registrations don't exist in the compiled binary at all. On Android the equivalent trigger is upgrading react-native-screens or react-native-safe-area-context to a new major version and re-running npx react-native run-android on a stale Gradle build — Gradle's incremental build can skip regenerating the Codegen artifacts and PackageList.java, so the JS side asks for a component name the compiled libreact_codegen_*.so doesn't have.
The Expo-managed equivalent is bumping react-native-screens or react-native-safe-area-context in package.json and running npx expo start against an existing development build you didn't rebuild:
bash
npx create-expo-app@latest repro --template default
cd repro
npx expo install react-native-screens react-native-safe-area-context @react-navigation/native @react-navigation/native-stack
npx expo start # still pointed at the old dev client binaryapp.json (relevant slice):
json
{
"expo": {
"name": "repro",
"slug": "repro",
"sdkVersion": "57.0.0",
"plugins": ["expo-router"]
}
}Triggers you'll recognize from the field: Android only right after a react-native-screens major bump with no ./gradlew clean; iOS only after npm install without pod install; Expo Go rejecting the app outright with a version-mismatch banner instead of this error (Expo Go ships a fixed set of native modules and can't pick up a new one at all — see the Expo Go article); and a physical device crashing while a simulator with an older cached build shows the placeholder box because it never picked up the new JS in the first place.
3. Version Behaviour Matrix
| RN version | Architecture | What you see |
|---|---|---|
| 0.79 – 0.80 | Legacy default, New Arch opt-in | Legacy: hard Invariant Violation … was not found in the UIManager crash. New Arch: Unimplemented component: <X> placeholder, occasionally cascading into a crash inside native-stack headers. |
| 0.81 | Legacy still selectable | Same two wordings; identical root cause. |
| 0.82 | New Architecture only | Legacy wording disappears entirely — every unregistered view now surfaces as Unimplemented component: <X> or the ViewConfig for component X not found warning. |
| 0.83 | New Architecture only | No change to this error path; this was the first RN release with no user-facing breaking changes. |
| 0.84 | New Architecture only, Legacy Architecture components removed | Libraries that still ship only a legacy ViewManager with no Fabric ComponentDescriptor fail hard here — there is no interop fallback left to catch them. |
| 0.85 – 0.86 | New Architecture only | No change to this error path. |
| 0.87 | New Architecture only | No change to this error path; if you're chasing a related build error, note the #import <React/RCTAppDelegate.h> header path change and Strict TypeScript API default landed this release, not this one. |
Framework-layer versions worth pinning down when this fires through navigation: Expo SDK 55 ships RN 0.83, SDK 56 ships RN 0.85, SDK 57 ships RN 0.86. React Navigation 7's native-stack requires a react-native-screens major recent enough to have a Fabric ComponentDescriptor for every header subview it uses — mixing a new @react-navigation/native-stack with an old, pre-Fabric react-native-screens from a stale lockfile reproduces this error without touching any RN version at all.
4. Why It Happens — Surface Level
The JavaScript bundle asks to render a native view by name (RNSScreen, RNSScreenStackHeaderConfig, RNCSafeAreaProvider, or whatever your own native module calls itself). The compiled native app — the .app/.ipa on iOS, the .apk on Android — doesn't have that view registered. Almost always, that's because the JS side moved forward (a new npm install, a new Expo package, a lockfile bump) and the native side didn't: no pod install, no clean Gradle build, no new development build, no expo prebuild --clean. Less often, the library itself simply hasn't shipped a Fabric implementation for that view yet.
5. Why It Happens — Under the Hood
Fabric doesn't resolve view names dynamically off a runtime registry the way the legacy bridge did. Each native view is described by a ComponentDescriptor, generated at build time by Codegen from either a TypeScript spec (codegenNativeComponent<Props>('RNSScreen')) or a manually written descriptor for libraries predating Codegen. That descriptor is what lets Fabric build the shadow tree, run Yoga against the view's props, commit, and mount it on the UI thread — the descriptor has to exist in the compiled binary before any JS runs, because it's C++ generated at native-build time, not JS resolved at bundle-load time. npm install changes only the JS side; it can't retroactively add a ComponentDescriptor to a binary you already built. That's the entire failure in one sentence: the JS bundle is ahead of the native binary.
React Native ships an interop layer so a library that hasn't migrated to Fabric yet can still render under the New Architecture: an automatic component descriptor (UnstableLegacyViewManagerAutomaticComponentDescriptor in RN's own source) wraps the library's old ViewManager so Fabric can treat it as if it had a real one. That's why plenty of not-yet-migrated libraries "just work" under Fabric with no code changes — and why the failure is inconsistent across libraries: if that interop registration for the component name isn't wired up — the library predates the mechanism, ships a Fabric descriptor for only some of its views, or autolinking excludes it — Fabric has no descriptor and no fallback, so you get the bare ViewConfig for component X not found warning followed by the placeholder or the crash.
On the legacy bridge, the equivalent lookup was UIManager.getViewManagerConfig('RNSScreen'), called from inside requireNativeComponent, checking a table populated when native modules registered themselves with the bridge at app startup — via RCTBridgeModule/@ReactModule and autolinking's generated PackageList.java (Android) and Podspec-driven module list (iOS). If autolinking never ran for that package — no pod install, or a react-native.config.js that explicitly excludes the package's platform — the entry is simply absent from the table, and the lookup throws synchronously on the JS thread the first time that component tries to render, which is why the stack trace bottoms out at requireNativeComponent and AppRegistry.runApplication: it's a startup-path failure, not a rendering bug in your own code.
Evidence worth pulling before you guess at the cause:
bash
npx react-native info
cd ios && pod list | grep -i RNScreens; cd ..
grep -r "react-native-screens" android/app/build/generated/autolinking/ 2>/dev/nullIf pod list doesn't show the pod, or the generated autolinking manifest doesn't mention the package, native registration never happened — no amount of JS-side debugging will fix it.
6. The Fix
Quick unblock (bare RN CLI): do the native install step you skipped, then do a full clean rebuild — don't just re-run Metro.
diff
npm install
+ cd ios && pod install --repo-update && cd ..
npx react-native run-iosdiff
npm install
+ cd android && ./gradlew clean && cd ..
npx react-native run-androidExpo (development build): a JS-only reload can never fix this — you need a new native build that includes the updated package.
bash
npx expo prebuild --clean
npx expo run:ios
npx expo run:androidIf you're on EAS Build rather than local builds, trigger a fresh development build instead of relying on expo start:
bash
eas build --profile development --platform allMonorepo / stale Gradle cache: confirm autolinking actually picked the package up before rebuilding again blind.
bash
cat android/app/build/generated/autolinking/autolinking.json | grep -A2 react-native-screensIf it's missing, check react-native.config.js for a stray exclusion:
diff
module.exports = {
dependencies: {
- 'react-native-screens': { platforms: { android: null } },
},
};Library genuinely hasn't shipped Fabric support for that view: check the library's own issue tracker for the exact component name before spending time on your own config — this is the one case no rebuild fixes. Options in order of preference: upgrade to the version that added the ComponentDescriptor (most actively maintained navigation and UI libraries have shipped this for years at this point); as a stopgap, patch-package a ComponentDescriptorProvider in following the library's existing pattern for a sibling view; last resort, pin the specific dependency to the last version with a working interop registration while you wait, and track the upstream issue rather than disabling the New Architecture project-wide — since 0.82, that's not an available escape hatch anyway.
What doesn't actually fix it, and what it costs: npx react-native start --reset-cache alone — this is a JS-bundler cache, and the missing piece is a native binary, so it does nothing here; it becomes a superstition that costs you a Metro restart every time this error shows up for the wrong reason. Deleting and reinstalling node_modules without touching ios/Pods or doing a Gradle clean — same problem, wrong layer.
Always finish with the full clean-rebuild recipe when you're not sure which layer is stale:
bash
watchman watch-del-all
rm -rf node_modules && npm install
npx react-native start --reset-cache
cd ios && rm -rf Pods Podfile.lock build && pod install --repo-update && cd ..
cd android && ./gradlew clean && cd ..
rm -rf ~/Library/Developer/Xcode/DerivedData/*7. Best Practices & The Better Design
Treat "added or upgraded a native-view package" and "ran a JS-only reload" as mutually exclusive states, and build that into your workflow rather than relying on memory: any change to a dependency that ships native code gets a native rebuild in the same commit, not a follow-up "oh right" a day later. For Expo projects, that means committing to Continuous Native Generation instead of hand-edited ios//android/ folders — a config-plugin-driven app.config.ts regenerates native projects deterministically from npx expo prebuild --clean, so there's no stale generated file to diverge from your package.json in the first place:
ts
// app.config.ts
import { ExpoConfig } from 'expo/config';
const config: ExpoConfig = {
name: 'repro',
slug: 'repro',
plugins: ['expo-router'],
};
export default config;Use npx expo install (not bare npm install) for any Expo-adjacent native package — it pins to the version tested against your installed SDK, which is exactly the mismatch class that produces this error when React Navigation and react-native-screens drift apart.
8. How to Prevent It Long-Term
Run npx expo-doctor (Expo) or npx react-native doctor (bare) in CI whenever package.json changes, so a version mismatch between react-native-screens, react-native-safe-area-context, and your installed React Native fails the PR instead of a developer's simulator. In a monorepo, add @rnx-kit/align-deps so every workspace resolves these native-view libraries to one version. Keep a CI job that does a genuine clean native build on both platforms on every merge — not just a Metro bundle check — since this error is invisible to a JS-only test suite; it only exists at the boundary between the JS bundle and the compiled binary. Scope Renovate or Dependabot to bump react-native, react-native-screens, react-native-safe-area-context, and react-navigation together in one PR, and route every RN minor bump through the Upgrade Helper so autolinking and Podfile changes aren't missed by hand. Don't rely on a screenshot review to catch this — the placeholder box is easy to miss in a quick QA pass — so add a check that fails CI on any new ViewConfig for component … not found warning in LogBox output.
9. Key Takeaways
Unimplemented component: <X>(Fabric) andrequireNativeComponent: "X" was not found in the UIManager(legacy bridge) are the same underlying failure — a native view the JS bundle expects that the compiled binary doesn't have — worded differently because RN 0.82 made the New Architecture mandatory and retired the legacy lookup path entirely.- The fix is never a JS-side change:
npm installorexpo installupdates the JS bundle only, and this error only clears once you do the matching native step —pod install, a Gradle clean, orexpo prebuild --cleanfollowed by a fresh development build. - On Fabric this fails softer than the old bridge did — a placeholder box and a warning instead of an instant crash — except inside layout-critical views like native-stack headers, where the missing measurements can still cascade into a real crash a few frames later.
- Before touching your own config, confirm the native side actually registered the component (
pod list, the generated autolinking manifest) — if it isn't there, no Metro cache reset or JS debugging will fix it. - This is the sibling failure to
TurboModuleRegistry.getEnforcing(...) could not be found: same "JS ahead of native binary" root cause and the same clean-rebuild fix, but for a<View>instead of a native method call.
CODELZ Newsletter
Join the newsletter to receive the latest updates in your inbox.