Skip to content

Commit ef28a2c

Browse files
alanleedevmeta-codesync[bot]
authored andcommitted
Add DEBUG guard and production error logging to UnimplementedView (#57980)
Summary: Pull Request resolved: #57980 Two changes to UnimplementedView on both iOS and Android: 1. **iOS: Add `#if DEBUG` guard** — the red overlay and error text in `RCTUnimplementedViewComponentView` and `RCTUnimplementedNativeComponentView` were shown in release builds (unlike Android which already had `ReactBuildConfig.DEBUG` guards). Users saw a red semi-transparent overlay with the component name. Now matches Android behavior — release users see nothing. 2. **Both platforms: Add production error logging** — `RCTLogError` on iOS and `ReactSoftExceptionLogger` on Android, outside the DEBUG guard. These fire in all builds including production, so missing native component registrations are reported to error dashboards instead of being completely silent. Files changed: - `RCTUnimplementedViewComponentView.mm` — primary Fabric fallback view (iOS) - `RCTUnimplementedNativeComponentView.mm` — UnimplementedNativeView component (iOS) - `ReactUnimplementedView.kt` — Android equivalent Changelog: [iOS][Fixed] - Hide unimplemented native component placeholders outside development builds [General][Changed] - Log missing native component registrations on iOS and Android --- Reviewed By: javache Differential Revision: D101001824 fbshipit-source-id: df88c28052b005ff68cbd7600509c290d3df4363
1 parent b743cb5 commit ef28a2c

3 files changed

Lines changed: 67 additions & 5 deletions

File tree

packages/react-native/React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.mm

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#import <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
1212
#import <react/renderer/components/FBReactNativeSpec/Props.h>
1313

14+
#import <React/RCTLog.h>
15+
1416
using namespace facebook::react;
1517

1618
@implementation RCTUnimplementedNativeComponentView {
@@ -24,12 +26,14 @@ - (instancetype)initWithFrame:(CGRect)frame
2426

2527
CGRect bounds = self.bounds;
2628
_label = [[UILabel alloc] initWithFrame:bounds];
29+
#if RCT_DEV
2730
_label.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.3];
31+
_label.textColor = [UIColor whiteColor];
32+
#endif
2833
_label.layoutMargins = UIEdgeInsetsMake(12, 12, 12, 12);
2934
_label.lineBreakMode = NSLineBreakByWordWrapping;
3035
_label.numberOfLines = 0;
3136
_label.textAlignment = NSTextAlignmentCenter;
32-
_label.textColor = [UIColor whiteColor];
3337

3438
self.contentView = _label;
3539
}
@@ -50,7 +54,20 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
5054
const auto &newViewProps = static_cast<const UnimplementedNativeViewProps &>(*props);
5155

5256
if (oldViewProps.name != newViewProps.name) {
53-
_label.text = [NSString stringWithFormat:@"'%s' is not Fabric compatible yet.", newViewProps.name.c_str()];
57+
const std::string &name = newViewProps.name;
58+
#if RCT_DEV
59+
_label.text = [NSString stringWithFormat:@"'%s' is not Fabric compatible yet.", name.c_str()];
60+
#endif
61+
// Skip the empty initial prop-default pass — only log once the real component
62+
// name has been propagated.
63+
if (!name.empty()) {
64+
// Log in all builds so missing components are reported in production.
65+
RCTLogError(
66+
@"UnimplementedNativeView: '%s' is not Fabric compatible yet. "
67+
"Ensure the iOS library has migrated this component to Fabric and registered "
68+
"a plugin entry for it.",
69+
name.c_str());
70+
}
5471
}
5572

5673
[super updateProps:props oldProps:oldProps];

packages/react-native/React/Fabric/Mounting/ComponentViews/UnimplementedView/RCTUnimplementedViewComponentView.mm

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#import <react/renderer/components/unimplementedview/UnimplementedViewShadowNode.h>
1616

1717
#import <React/RCTConversions.h>
18+
#import <React/RCTLog.h>
1819

1920
#import "RCTFabricComponentsPlugins.h"
2021

@@ -30,11 +31,13 @@ - (instancetype)initWithFrame:(CGRect)frame
3031
_props = UnimplementedViewShadowNode::defaultSharedProps();
3132

3233
_label = [[UILabel alloc] initWithFrame:self.bounds];
34+
#if RCT_DEV
3335
_label.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.3];
36+
_label.textColor = [UIColor whiteColor];
37+
#endif
3438
_label.lineBreakMode = NSLineBreakByCharWrapping;
3539
_label.numberOfLines = 0;
3640
_label.textAlignment = NSTextAlignmentCenter;
37-
_label.textColor = [UIColor whiteColor];
3841
_label.allowsDefaultTighteningForTruncation = YES;
3942
_label.adjustsFontSizeToFitWidth = YES;
4043

@@ -57,8 +60,19 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
5760
const auto &newUnimplementedViewProps = static_cast<const UnimplementedViewProps &>(*props);
5861

5962
if (oldUnimplementedViewProps.getComponentName() != newUnimplementedViewProps.getComponentName()) {
60-
_label.text =
61-
[NSString stringWithFormat:@"Unimplemented component: <%s>", newUnimplementedViewProps.getComponentName()];
63+
const char *componentName = newUnimplementedViewProps.getComponentName();
64+
#if RCT_DEV
65+
_label.text = [NSString stringWithFormat:@"Unimplemented component: <%s>", componentName];
66+
#endif
67+
// Skip the empty initial prop-default pass — only log once the real component
68+
// name has been propagated.
69+
if (componentName != nullptr && *componentName != '\0') {
70+
// Log in all builds so missing components are reported in production.
71+
RCTLogError(
72+
@"UnimplementedView: native component '%s' is not registered. "
73+
"Ensure the iOS library defines a plugin entry for this component.",
74+
componentName);
75+
}
6276
}
6377

6478
[super updateProps:props oldProps:oldProps];

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/unimplementedview/ReactUnimplementedView.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ import android.graphics.Color
1212
import android.view.Gravity
1313
import android.widget.LinearLayout
1414
import androidx.appcompat.widget.AppCompatTextView
15+
import com.facebook.react.bridge.ReactNoCrashSoftException
16+
import com.facebook.react.bridge.ReactSoftExceptionLogger
1517
import com.facebook.react.common.build.ReactBuildConfig
1618

1719
internal class ReactUnimplementedView(context: Context) : LinearLayout(context) {
1820

1921
private val textView: AppCompatTextView = AppCompatTextView(context)
22+
private var lastName: String? = null
2023

2124
init {
2225
textView.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)
@@ -33,8 +36,36 @@ internal class ReactUnimplementedView(context: Context) : LinearLayout(context)
3336
}
3437

3538
internal fun setName(name: String) {
39+
// @ReactProp setters are invoked on every prop update, not only on change. Gate on
40+
// an actual name change to mirror the iOS Fabric path (which only logs when
41+
// oldProps.componentName != newProps.componentName) and avoid soft-exception spam
42+
// from re-renders or recycled view instances.
43+
if (name == lastName) {
44+
return
45+
}
46+
lastName = name
47+
3648
if (ReactBuildConfig.DEBUG) {
3749
textView.text = "'$name' is not registered."
3850
}
51+
52+
// Skip empty names — these come from the initial prop-default pass before the real
53+
// component name is set, and would produce noisy "''" entries in dashboards.
54+
if (name.isEmpty()) {
55+
return
56+
}
57+
58+
// Log in all builds so missing components are reported in production.
59+
ReactSoftExceptionLogger.logSoftException(
60+
TAG,
61+
ReactNoCrashSoftException(
62+
"UnimplementedView: native component '$name' is not registered. " +
63+
"Ensure the native library defines a ViewManager for this component.",
64+
),
65+
)
66+
}
67+
68+
companion object {
69+
private const val TAG = "ReactUnimplementedView"
3970
}
4071
}

0 commit comments

Comments
 (0)