Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#import <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
#import <react/renderer/components/FBReactNativeSpec/Props.h>

#import <React/RCTLog.h>

using namespace facebook::react;

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

CGRect bounds = self.bounds;
_label = [[UILabel alloc] initWithFrame:bounds];
#if RCT_DEV
_label.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.3];
_label.textColor = [UIColor whiteColor];
#endif
_label.layoutMargins = UIEdgeInsetsMake(12, 12, 12, 12);
_label.lineBreakMode = NSLineBreakByWordWrapping;
_label.numberOfLines = 0;
_label.textAlignment = NSTextAlignmentCenter;
_label.textColor = [UIColor whiteColor];

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

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

[super updateProps:props oldProps:oldProps];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import <react/renderer/components/unimplementedview/UnimplementedViewShadowNode.h>

#import <React/RCTConversions.h>
#import <React/RCTLog.h>

#import "RCTFabricComponentsPlugins.h"

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

_label = [[UILabel alloc] initWithFrame:self.bounds];
#if RCT_DEV
_label.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.3];
_label.textColor = [UIColor whiteColor];
#endif
_label.lineBreakMode = NSLineBreakByCharWrapping;
_label.numberOfLines = 0;
_label.textAlignment = NSTextAlignmentCenter;
_label.textColor = [UIColor whiteColor];
_label.allowsDefaultTighteningForTruncation = YES;
_label.adjustsFontSizeToFitWidth = YES;

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

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

[super updateProps:props oldProps:oldProps];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import android.graphics.Color
import android.view.Gravity
import android.widget.LinearLayout
import androidx.appcompat.widget.AppCompatTextView
import com.facebook.react.bridge.ReactNoCrashSoftException
import com.facebook.react.bridge.ReactSoftExceptionLogger
import com.facebook.react.common.build.ReactBuildConfig

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

private val textView: AppCompatTextView = AppCompatTextView(context)
private var lastName: String? = null

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

internal fun setName(name: String) {
// @ReactProp setters are invoked on every prop update, not only on change. Gate on
// an actual name change to mirror the iOS Fabric path (which only logs when
// oldProps.componentName != newProps.componentName) and avoid soft-exception spam
// from re-renders or recycled view instances.
if (name == lastName) {
return
}
lastName = name

if (ReactBuildConfig.DEBUG) {
textView.text = "'$name' is not registered."
}

// Skip empty names — these come from the initial prop-default pass before the real
// component name is set, and would produce noisy "''" entries in dashboards.
if (name.isEmpty()) {
return
}

// Log in all builds so missing components are reported in production.
ReactSoftExceptionLogger.logSoftException(
TAG,
ReactNoCrashSoftException(
"UnimplementedView: native component '$name' is not registered. " +
"Ensure the native library defines a ViewManager for this component.",
),
)
}

companion object {
private const val TAG = "ReactUnimplementedView"
}
}
Loading